From f1a651926dbe22449fc673a339341fb39fbc6f1a Mon Sep 17 00:00:00 2001 From: Christos Houtouridis Date: Tue, 23 Oct 2018 13:56:25 +0300 Subject: [PATCH] com: Traditionally the first module of my libs is an i2c implementation --- include/utl/com/i2c.h | 285 +++++++++++++++++++++++++++ include/utl/com/i2c_bb.h | 407 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 692 insertions(+) create mode 100644 include/utl/com/i2c.h create mode 100644 include/utl/com/i2c_bb.h diff --git a/include/utl/com/i2c.h b/include/utl/com/i2c.h new file mode 100644 index 0000000..321380d --- /dev/null +++ b/include/utl/com/i2c.h @@ -0,0 +1,285 @@ +/*! + * \file /utl/com/i2c.h + * \brief An Abstract base class interface for the i2c bus + * + * Copyright (C) 2018 Christos Choutouridis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + */ +#ifndef _utl_com_i2c_h__ +#define _utl_com_i2c_h__ + +#include +#include +#include + +namespace utl { + + /*! + * \ingroup Communication + * \brief Abstract base class for i2c bus + */ + //!@{ + + /*! + * \brief + * this class force a common interface for I2C communication + * protocol implementations using CRTP + * \param impl_t The CRTP type (the derived/implementation class typename). + */ + template + class i2c_i { + _CRTP_IMPL(impl_t); + + public: + using type = i2c_i; //!< Export type as identity meta-function + //! I2C transmit/receive sequence + enum class Sequence { + BYTE =0, //!< Only read/write byte [8 clocks] + ACK, //!< Only send/receive ack [1 clock] + BYTEnACK //!< Read/Write byte and ack [9 clocks] + }; + + /*! + * \name Object lifetime + */ + //!@{ + protected: + i2c_i () = default; //!< Allow constructor from derived only + ~i2c_i () = default; //!< Allow destructor from derived only + i2c_i (const type&) = delete; //!< No copies + type& operator= (const type&) = delete; + //!@} + + /*! + * \name Implementation requirements + * \note + * In order for the implementation to have the following as private members + * it also need to declare this class as friend + */ + //!@{ + //! uint32_t _frequency () const; < clock frequency of the bus [Hz] + //! void _frequency (uint32_t); < set clock frequency of the bus [Hz] + //! void _start(); < Send start functionality + //! void _stop(); < Send stop functionality + //! byte_t _read (bool ack, Sequence seq); < Receive a byte from the i2c bus. + //! bool _write (byte_t byte, Sequence seq);< Transmit a byte to the i2c bus. + //!@} + + /*! + * \name Get/Set functions + */ + //!@{ + public: + uint32_t frequency () const { return impl()._frequency (); } //!< \return clock frequency of the bus + void frequency (uint32_t f) { impl()._frequency (f); } //!< \brief set clock frequency of the bus + //!@} + + /*! + * \name User functions + */ + //!@{ + public: + void start() { impl()._start (); } //!< Send start functionality + void stop () { impl()._stop (); } //!< Send stop functionality + + /*! + * \brief + * Receive a byte from the i2c bus. + * \param ack Optional ack bit. + * \arg 1 ACK the reception + * \arg 0 Don't ACK the reception. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Receive only the byte, do not send ack clock + * \arg Sequence::ACK Send only the ack bit + * \arg Sequence::BYTEnACK Receive the byte and send the ack bit + * \return The byte received. + */ + byte_t read (bool ack, Sequence seq =Sequence::BYTEnACK) { + return impl()._read (ack, seq); + } + + /*! + * \brief + * Transmit a byte to the i2c bus. + * \param byte The byte to send. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Transmit only the byte, do not read ack bit + * \arg Sequence::ACK Read only the ack bit + * \arg Sequence::BYTEnACK Transmit the byte and read the ack bit + * \return Slave's ACK bit + * \arg false Slave didn't ACK + * \arg true Slave did ACK + */ + bool write (byte_t byte, Sequence seq =Sequence::BYTEnACK) { + return impl()._write (byte, seq); + } + //!@} + + }; + + /*! + * \brief + * A virtual base class specialization + * \param impl_t = virtual_tag + */ + template<> + class i2c_i { + public: + using type = i2c_i ; //!< Export type as identity meta-function + + //! I2C transmit/receive sequence + enum class Sequence { + BYTE=0, //!< Only read/write byte [8 clocks] + ACK, //!< Only send/receive ack [1 clock] + BYTEnACK //!< Read/Write byte and ack [9 clocks] + }; + + /*! + * \name Object lifetime + */ + //!@{ + protected: + i2c_i () = default; //!< Allow constructor from derived only + i2c_i (const type&) = delete; //!< No copies + type& operator= (const type&) = delete; + public: + virtual ~i2c_i () = default; //!< Virtual default destructor + //!@} + + /*! + * \name Implementation requirements + */ + //!@{ + private: + virtual uint32_t _frequency () const = 0; //!< \return clock frequency of the bus [Hz] + virtual void _frequency (uint32_t) = 0; //!< \brief set clock frequency of the bus [Hz] + virtual void _start() = 0; //!< Send start functionality + virtual void _stop() = 0; //!< Send stop functionality + virtual byte_t _read (bool ack, Sequence seq) = 0; //!< Receive a byte from the i2c bus. + virtual bool _write (byte_t byte, Sequence seq) = 0; //!< Transmit a byte to the i2c bus. + //!@} + /*! + * \name Get/Set functions + */ + //!@{ + public: + uint32_t frequency () const { return _frequency(); } //!< \return clock frequency of the bus [Hz] + void frequency (uint32_t f) { _frequency(f); } //!< \brief set clock frequency of the bus [Hz] + //!@} + + /*! + * \name User functions + */ + //!@{ + public: + void start () { _start(); } + void stop () { _stop(); } + /*! + * \brief + * Receive a byte from the i2c bus. + * \param ack Optional ack bit. + * \arg 1 ACK the reception + * \arg 0 Don't ACK the reception. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Receive only the byte, do not send ack clock + * \arg Sequence::ACK Send only the ack bit + * \arg Sequence::BYTEnACK Receive the byte and send the ack bit + * \return The byte received. + */ + byte_t read (bool ack, Sequence seq =Sequence::BYTEnACK) { + return _read (ack, seq); + } + /*! + * \brief + * Transmit a byte to the i2c bus. + * \param byte The byte to send. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Transmit only the byte, do not read ack bit + * \arg Sequence::ACK Read only the ack bit + * \arg Sequence::BYTEnACK Transmit the byte and read the ack bit + * \return Slave's ACK bit + * \arg false Slave didn't ACK + * \arg true Slave did ACK + */ + bool write (byte_t byte, Sequence seq =Sequence::BYTEnACK) { + return _write (byte, seq); + } + //!@} + + }; + +#if defined _utl_have_concepts + /*! + * i2c interface concept + */ + template + concept bool i2c_c = requires (T t, const T ct, typename T::Sequence s) { + // Object type + requires not_::value>::value; + requires not_::value>::value; + // Methods + {ct.frequency()} -> uint32_t; + {t.frequency(0)} -> void; + {t.start()} -> void; + {t.stop()} -> void; + {t.read (1, s)} -> byte_t; + {t.write(0, s)} -> bool; + }; +#else + namespace i2c_t_cnpt { + using std::declval; + + template using try_cfreq_t = decltype (declval().frequency()); + template using try_freq_t = decltype (declval<_Tp>().frequency(declval())); + template using try_start_t = decltype (declval<_Tp>().start()); + template using try_stop_t = decltype (declval<_Tp>().stop()); + template using try_read_t + = decltype (declval<_Tp>().read (declval(), declval())); + template using try_write_t + = decltype (declval<_Tp>().write (declval(), declval())); + + //! Primary template to catch any non I2C interface types + template + struct is_i2c_ : false_ { }; + + //! template to catch a proper I2C interface type + template + struct is_i2c_ <_Tp, + void_t < + typename _Tp::Sequence, + use_if_same_t >, + use_if_same_t >, + use_if_same_t >, + use_if_same_t >, + use_if_same_t >, + use_if_same_t > + > //!^ SFINAE may apply inside if_same_t<> also + > : true_ { }; + } + /*! + * Value meta-programming function alias for I2C interface checking + * \param _Tp Type to check + * \return True if _Tp is a i2c interface + */ + template + constexpr bool i2c_c = i2c_t_cnpt::is_i2c_<_Tp>::value; +#endif + + //!@} + +} // namespace utl + +#endif // _utl_com_i2c_h__ diff --git a/include/utl/com/i2c_bb.h b/include/utl/com/i2c_bb.h new file mode 100644 index 0000000..a508e70 --- /dev/null +++ b/include/utl/com/i2c_bb.h @@ -0,0 +1,407 @@ +/*! + * \file utl/com/i2c_bb.h + * \brief A bit banking implementation of i2c bus inherited from + * i2c_i base class. + * + * Copyright (C) 2018 Christos Choutouridis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + */ +#ifndef __utl_com_i2c_bb_h__ +#define __utl_com_i2c_bb_h__ + +#include +#include +#include + + +namespace utl { + + /*! + * \ingroup Communication + * \brief A bit banking implementation of i2c bus + * inherited from \ref i2c_i base class. + * \sa i2c_i + */ + //!@{ + + /*! + * \brief + * I2C bit banking interface template class providing + * an I2C using bit banking using CRTP. + * \param impl_t The CRTP type (the derived/implementation class typename). + */ + template + class i2c_bb_i : public i2c_i> { + _CRTP_IMPL(impl_t); + friend i2c_i>; + + public: + using type = i2c_bb_i; //!< Export type as identity meta-function + using Sequence = typename i2c_i::Sequence; + //! SDA pin direction enumerator + enum class SDAMode { + INPUT =0, + OUTPUT + }; + /*! + * \name Object lifetime + */ + //!@{ + protected: + //! \brief A default destructor, allow destructor from derived only + ~i2c_bb_i () noexcept = default; + //! \brief A default constructor + i2c_bb_i (uint32_t frequency =100000) noexcept + : usec_ {1000000/(2*frequency)} { } + //!@} + + /*! + * \name Implementation requirements + * \note + * In order for the implementation to have the following as private members + * it also need to declare this class as friend + * bool SDA (SDAMode mode, bool st); + * void SCL (uint8_t st); + * void delay (uint32_t usec); + */ + //!@{ + private: + /*! + * Implementers's sda pin function + * \param st In SDA_OUTPUT mode, selects the pin output state + * \return In SDA_INPUT mode return the pin input state + */ + bool SDA (SDAMode mode, bool st) { return impl().SDA (mode, st); } + void SCL (uint8_t st) { impl().SCL (st); } //!< Implementers's scl pin function + void delay (uint32_t usec) { impl().delay(usec); } //!< Implementers's usec delay function + //!@} + + /*! + * \name Implementation of base requirements + */ + //!@{ + private: + uint32_t _frequency () const { return 1000000/(2*usec_); } + void _frequency (uint32_t f) { usec_ = 1000000/(2*f); } + + void _start (); //!< Send start functionality + void _stop (); //!< Send stop functionality + byte_t _read (bool ack, Sequence seq); + bool _write (byte_t byte, Sequence seq); + uint32_t usec_; //!< half period of I2C nus + //!@} + }; + + /* + * ============= User functions ================ + */ + /*! + * \brief + * Send a START bit to the bus + * \return none + */ + template + void i2c_bb_i::_start (void) { + //Initially set pins + SDA (SDAMode::OUTPUT, 1); + SCL (1); + delay (usec_); + SDA (SDAMode::OUTPUT, 0); + delay (usec_); + SCL (0); //Clear Clock + } + + /*! + * \brief + * Send a START bit to the bus + * \return none + */ + template + void i2c_bb_i::_stop (void) { + //Stop bit Operation + SDA (SDAMode::OUTPUT, 0); + SCL (0); + SCL (1); + delay (usec_); + SDA (SDAMode::OUTPUT, 1); + delay (usec_); + } + + /*! + * \brief + * Receive a byte from the i2c bus. + * \param ack Optional ack bit. + * \arg 1 ACK the reception + * \arg 0 Don't ACK the reception. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Receive only the byte, do not send ack clock + * \arg Sequence::ACK Send only the ack bit + * \arg Sequence::BYTEnACK Receive the byte and send the ack bit + * \return The byte received. + */ + template + byte_t i2c_bb_i::_read (bool ack, Sequence seq) { + byte_t byte {0}; + //Initial conditions + SCL (0); + SDA (SDAMode::INPUT, 0); + + if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) { + // read 8 data bits + for (int i=8 ; i!=0 ; --i) { + byte <<= 1; + SCL (1); + delay (usec_); + byte |= SDA (SDAMode::INPUT, 0); + SCL (0); + delay (usec_); + } + } + if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) { + SDA (SDAMode::OUTPUT, !ack); //Send (or not) ACK bit + SCL (1); + delay (usec_); + SCL (0); // Keep the bus busy + SDA (SDAMode::OUTPUT, 0); + } + return byte; + } + + /*! + * \brief + * Transmit a byte to the i2c bus. + * \param byte The byte to send. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Transmit only the byte, do not read ack bit + * \arg Sequence::ACK Read only the ack bit + * \arg Sequence::BYTEnACK Transmit the byte and read the ack bit + * \return Slave's ACK bit + * \arg false Slave didn't ACK + * \arg true Slave did ACK + */ + template + bool i2c_bb_i::_write (byte_t byte, Sequence seq) { + bool ack {false}; + //Initial conditions + SCL (0); + SDA (SDAMode::OUTPUT, 0); + + if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) { + //Send 8 bit data + for (int i=8 ; i!=0 ; --i) { + //Send MSB + SDA (SDAMode::OUTPUT, byte & 0x80); + byte <<= 1; + SCL (1); + delay (usec_); + SCL (0); + delay (usec_); + } + } + if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) { + // Get ACK + SDA (SDAMode::INPUT, 0); + SCL (1); + delay (usec_); + ack = !SDA (SDAMode::INPUT, 0); + SCL (0); // Keep the bus busy + delay (usec_); + SDA (SDAMode::OUTPUT, 0); + } + return ack; + } + + + /*! + * \brief + * A virtual base class interface specialization. + * Using the private virtual interface we provide the interface from + * i2c_i + * \param impl_t = virtual_tag + */ + template<> + class i2c_bb_i : public i2c_i { + public: + using type = i2c_bb_i; //!< Export type as identity meta-function + using Sequence = typename i2c_i::Sequence; + //! SDA pin direction enumerator + enum class SDAMode { + INPUT =0, + OUTPUT + }; + + /*! + * \name Object lifetime + */ + //!@{ + protected: + //! \brief Constructor + i2c_bb_i (uint32_t frequency =100000) noexcept + : usec_ {1000000/(2*frequency)} { } + //! \brief Virtual destructor + virtual ~i2c_bb_i () noexcept = default; + //!@} + + /*! + * \name Implementation requirements + */ + //!@{ + private: + /*! + * Implementers's sda pin function + * \param st In SDA_OUTPUT mode, selects the pin output state + * \return In SDA_INPUT mode return the pin input state + */ + virtual bool SDA (SDAMode mode, bool st) =0; + virtual void SCL (bool st) =0; //!< Implementers's scl pin function + virtual void delay (uint32_t usec) =0; //!< Implementers's usec delay function + //!@} + + /*! + * \name Implementation of base requirements + */ + //!@{ + private: + uint32_t _frequency () const final { return 1000000/(2*usec_); } + void _frequency (uint32_t f) final { usec_ = 1000000/(2*f); } + + void _start () final; + void _stop () final; + byte_t _read (bool ack, Sequence seq) final; + bool _write (byte_t byte, Sequence seq) final; + //!< half period of I2C bus + uint32_t usec_; + //!@} + }; + + /*! + * \brief + * Send a START bit to the bus + * \return none + */ + void i2c_bb_i::_start (void) { + //Initially set pins + SDA (SDAMode::OUTPUT, 1); + SCL (1); + delay (usec_); + SDA (SDAMode::OUTPUT, 0); + delay (usec_); + SCL (0); //Clear Clock + } + + /*! + * \brief + * Send a START bit to the bus + * \return none + */ + void i2c_bb_i::_stop (void) { + //Stop bit Operation + SDA (SDAMode::OUTPUT, 0); + SCL (0); + SCL (1); + delay (usec_); + SDA (SDAMode::OUTPUT, 1); + delay (usec_); + } + + /*! + * \brief + * Receive a byte from the i2c bus. + * \param ack Optional ack bit. + * \arg 1 ACK the reception + * \arg 0 Don't ACK the reception. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Receive only the byte, do not send ack clock + * \arg Sequence::ACK Send only the ack bit + * \arg Sequence::BYTEnACK Receive the byte and send the ack bit + * \return The byte received. + */ + byte_t i2c_bb_i::_read (bool ack, Sequence seq) { + byte_t byte {0}; + //Initial conditions + SCL (0); + SDA (SDAMode::INPUT, 0); + + if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) { + // read 8 data bits + for (int i=8 ; i!=0 ; --i) { + byte <<= 1; + SCL (1); + delay (usec_); + byte |= SDA (SDAMode::INPUT, 0); + SCL (0); + delay (usec_); + } + } + if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) { + SDA (SDAMode::OUTPUT, !ack); //Send (or not) ACK bit + SCL (1); + delay (usec_); + SCL (0); // Keep the bus busy + SDA (SDAMode::OUTPUT, 0); + } + return byte; + } + + /*! + * \brief + * Transmit a byte to the i2c bus. + * \param byte The byte to send. + * \param seq The operation sequence to execute + * \arg Sequence::BYTE Transmit only the byte, do not read ack bit + * \arg Sequence::ACK Read only the ack bit + * \arg Sequence::BYTEnACK Transmit the byte and read the ack bit + * \return Slave's ACK bit + * \arg false Slave didn't ACK + * \arg true Slave did ACK + */ + bool i2c_bb_i::_write (byte_t byte, Sequence seq) { + bool ack {false}; + //Initial conditions + SCL (0); + SDA (SDAMode::OUTPUT, 0); + + if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) { + //Send 8 bit data + for (int i=8 ; i!=0 ; --i) { + //Send MSB + SDA (SDAMode::OUTPUT, byte & 0x80); + byte <<= 1; + SCL (1); + delay (usec_); + SCL (0); + delay (usec_); + } + } + if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) { + // Get ACK + SDA (SDAMode::INPUT, 0); + SCL (1); + delay (usec_); + ack = !SDA (SDAMode::INPUT, 0); + SCL (0); // Keep the bus busy + delay (usec_); + SDA (SDAMode::OUTPUT, 0); + } + return ack; + } + //!@} + +} // namspace utl + + +#endif // #ifndef __utl_com_i2c_bb_h__ +