|
- /*!
- * \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 <http://www.gnu.org/licenses/>.
- *
- */
- #ifndef __utl_com_i2c_bb_h__
- #define __utl_com_i2c_bb_h__
-
- #include <utl/core/impl.h>
- #include <utl/core/crtp.h>
- #include <utl/com/i2c.h>
-
-
- 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 <typename impl_t>
- class i2c_bb_i : public i2c_i<i2c_bb_i<impl_t>> {
- _CRTP_IMPL(impl_t);
- friend i2c_i<i2c_bb_i<impl_t>>;
-
- public:
- using type = i2c_bb_i<impl_t>; //!< Export type as identity meta-function
- using Sequence = typename i2c_i<type>::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 <typename impl_t>
- void i2c_bb_i<impl_t>::_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 <typename impl_t>
- void i2c_bb_i<impl_t>::_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 <typename impl_t>
- byte_t i2c_bb_i<impl_t>::_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 <typename impl_t>
- bool i2c_bb_i<impl_t>::_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<virtual_tag>
- * \param impl_t = virtual_tag
- */
- template<>
- class i2c_bb_i<virtual_tag> : public i2c_i<virtual_tag> {
- public:
- using type = i2c_bb_i<virtual_tag>; //!< Export type as identity meta-function
- using Sequence = typename i2c_i<virtual_tag>::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<virtual_tag>::_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<virtual_tag>::_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<virtual_tag>::_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<virtual_tag>::_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__
|