/*! * \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 clk) noexcept : usec_ {1000000/(2*clk)} { } //!@} /*! * \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 */ //!@{ 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 _clock () const { return 1000000/(2*usec_); } void _clock (uint32_t c) { usec_ = 1000000/(2*c); } void _start (); //!< Send start functionality void _stop (); //!< Send stop functionality byte_t _rx_data (bool ack, Sequence seq); bool _tx_data (byte_t byte, Sequence seq); uint32_t usec_; //!< half period of I2C bus //!@} }; /* * ============= 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::_rx_data (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::_tx_data (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 clk) noexcept : usec_ {1000000/(2*clk)} { } //! \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 _clock () const final { return 1000000/(2*usec_); } void _clock (uint32_t c) final { usec_ = 1000000/(2*c); } void _start () final; void _stop () final; byte_t _rx_data (bool ack, Sequence seq) final; bool _tx_data (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::_rx_data (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::_tx_data (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__