/* Arduino DigitalIO Library * Copyright (C) 2013 by William Greiman * * This file is part of the Arduino DigitalIO Library * * This Library is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This Library 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with the Arduino DigitalIO Library. If not, see * . */ #ifndef SOFT_I2C_MASTER_H #define SOFT_I2C_MASTER_H /** * @file * @brief AVR Software I2C library * * @defgroup softI2C Software I2C * @details Software Two Wire Interface library. * @{ */ #if defined(__AVR__) || defined(DOXYGEN) // AVR only #include #include #include "DigitalPin.h" #include "I2cConstants.h" //------------------------------------------------------------------------------ // State codes. /** Stop condition transmitted. */ const uint8_t STATE_STOP = 0; /** Repeated start condition transmitted. */ const uint8_t STATE_REP_START = 1; /** Read data transfer active. */ const uint8_t STATE_RX_DATA = 2; /** Write data transfer active. */ const uint8_t STATE_TX_DATA = 3; /** Slave address plus read bit transmitted, NACK received. */ const uint8_t STATE_RX_ADDR_NACK = 4; /** Slave address plus write bit transmitted, NACK received. */ const uint8_t STATE_TX_ADDR_NACK = 5; /** Data byte transmitted, NACK received. */ const uint8_t STATE_TX_DATA_NACK = 6; //============================================================================== /** * @class I2cMasterBase * @brief Base class for FastI2cMaster, SoftI2cMaster */ class I2cMasterBase { public: I2cMasterBase() : _state(STATE_STOP) {} /** Read a byte * * @note This function should only be used by experts. Data should be * accessed by calling transfer() and transferContinue() * * @param[in] last send a NACK to terminate read if last is true else * send an ACK to continue the read. * * @return byte read from I2C bus */ virtual uint8_t read(uint8_t last) = 0; /** Issue a start condition * * @note This function should only be used by experts. Data should be * accessed by calling transfer() and transferContinue() */ virtual void start() = 0; /** Issue a stop condition. * * @note This function should only be used by experts. Data should be * accessed by calling transfer() and transferContinue() */ virtual void stop() = 0; bool transfer(uint8_t addressRW, void *buf, size_t nbyte, uint8_t option = I2C_STOP); bool transferContinue(void *buf, size_t nbyte, uint8_t option = I2C_STOP); /** Write a byte * * @note This function should only be used by experts. Data should be * accessed by calling transfer() and transferContinue() * * @param[in] data byte to write * @return true for ACK or false for NACK */ virtual bool write(uint8_t data) = 0; private: uint8_t _state; }; //============================================================================== /** * @class SoftI2cMaster * @brief AVR Software I2C master class */ class SoftI2cMaster : public I2cMasterBase { public: SoftI2cMaster() {} SoftI2cMaster(uint8_t sclPin, uint8_t sdaPin); void begin(uint8_t sclPin, uint8_t sdaPin); uint8_t read(uint8_t last); void start(); void stop(void); bool write(uint8_t b); private: uint8_t _sclBit; uint8_t _sdaBit; volatile uint8_t* _sclDDR; volatile uint8_t* _sdaDDR; volatile uint8_t* _sdaInReg; //---------------------------------------------------------------------------- bool readSda() { return *_sdaInReg & _sdaBit; } //---------------------------------------------------------------------------- void sclDelay(uint8_t n) { _delay_loop_1(n); } //---------------------------------------------------------------------------- void writeScl(bool value) { uint8_t s = SREG; noInterrupts(); if (value == LOW) { // Pull scl low. *_sclDDR |= _sclBit; } else { // Put scl in high Z input mode. *_sclDDR &= ~_sclBit; } SREG = s; } //---------------------------------------------------------------------------- void writeSda(bool value) { uint8_t s = SREG; noInterrupts(); if (value == LOW) { // Pull sda low. *_sdaDDR |= _sdaBit; } else { // Put sda in high Z input mode. *_sdaDDR &= ~_sdaBit; } SREG = s; } }; //============================================================================== // Template based fast software I2C //------------------------------------------------------------------------------ /** * @class FastI2cMaster * @brief AVR Fast software I2C master class. */ template class FastI2cMaster : public I2cMasterBase { public: //---------------------------------------------------------------------------- FastI2cMaster() { begin(); } //---------------------------------------------------------------------------- /** Initialize I2C bus pins. */ void begin() { fastDigitalWrite(sclPin, LOW); fastDigitalWrite(sdaPin, LOW); sclWrite(HIGH); sdaWrite(HIGH); } //---------------------------------------------------------------------------- uint8_t read(uint8_t last) { uint8_t data = 0; sdaWrite(HIGH); readBit(7, &data); readBit(6, &data); readBit(5, &data); readBit(4, &data); readBit(3, &data); readBit(2, &data); readBit(1, &data); readBit(0, &data); // send ACK or NACK sdaWrite(last); sclDelay(4); sclWrite(HIGH); sclDelay(6); sclWrite(LOW); sdaWrite(LOW); return data; } //---------------------------------------------------------------------------- void start() { if (!fastDigitalRead(sdaPin)) { // It's a repeat start. sdaWrite(HIGH); sclDelay(8); sclWrite(HIGH); sclDelay(8); } sdaWrite(LOW); sclDelay(8); sclWrite(LOW); sclDelay(8); } //---------------------------------------------------------------------------- void stop(void) { sdaWrite(LOW); sclDelay(8); sclWrite(HIGH); sclDelay(8); sdaWrite(HIGH); sclDelay(8); } //---------------------------------------------------------------------------- bool write(uint8_t data) { // write byte writeBit(7, data); writeBit(6, data); writeBit(5, data); writeBit(4, data); writeBit(3, data); writeBit(2, data); writeBit(1, data); writeBit(0, data); // get ACK or NACK sdaWrite(HIGH); sclWrite(HIGH); sclDelay(5); bool rtn = fastDigitalRead(sdaPin); sclWrite(LOW); sdaWrite(LOW); return rtn == 0; } private: //---------------------------------------------------------------------------- inline __attribute__((always_inline)) void sclWrite(bool value) { fastDdrWrite(sclPin, !value); } //---------------------------------------------------------------------------- inline __attribute__((always_inline)) void sdaWrite(bool value) { fastDdrWrite(sdaPin, !value); } //---------------------------------------------------------------------------- inline __attribute__((always_inline)) void readBit(uint8_t bit, uint8_t* data) { sclWrite(HIGH); sclDelay(5); if (fastDigitalRead(sdaPin)) { *data |= 1 << bit; } sclWrite(LOW); if (bit) { sclDelay(6); } } //---------------------------------------------------------------------------- void sclDelay(uint8_t n) { _delay_loop_1(n); } //---------------------------------------------------------------------------- inline __attribute__((always_inline)) void writeBit(uint8_t bit, uint8_t data) { uint8_t mask = 1 << bit; sdaWrite(data & mask); sclWrite(HIGH); sclDelay(5); sclWrite(LOW); sclDelay(5); } }; #endif // __AVR__ #endif // SOFT_I2C_MASTER_H /** @} */