/* 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 * . */ /** * @file * @brief Software I2C library * * @defgroup softI2C Software I2C * @details Software Two Wire Interface library. * @{ */ #include "SoftI2cMaster.h" //------------------------------------------------------------------------------ /** * Start an I2C transfer with possible continuation. * * @param[in] addrRW I2C slave address plus R/W bit. * The I2C slave address is in the high seven bits * and is ORed with on of the following: * - I2C_READ for a read transfer. * - I2C_WRITE for a write transfer. * . * @param[in,out] buf Source or destination for transfer. * @param[in] nbytes Number of bytes to transfer (may be zero). * @param[in] option Option for ending the transfer, one of: * - I2C_STOP end the transfer with an I2C stop * condition. * - I2C_REP_START end the transfer with an I2C * repeated start condition. * - I2C_CONTINUE allow additional transferContinue() * calls. * . * @return true for success else false. */ bool I2cMasterBase::transfer(uint8_t addrRW, void *buf, size_t nbytes, uint8_t option) { if (_state != STATE_REP_START) { start(); } if (!write(addrRW)) { _state = addrRW & I2C_READ ? STATE_RX_ADDR_NACK : STATE_TX_ADDR_NACK; return false; } _state = addrRW & I2C_READ ? STATE_RX_DATA : STATE_TX_DATA; return transferContinue(buf, nbytes, option); } //------------------------------------------------------------------------------ /** * Continue an I2C transfer. * * @param[in,out] buf Source or destination for transfer. * @param[in] nbytes Number of bytes to transfer (may be zero). * @param[in] option Option for ending the transfer, one of: * - I2C_STOP end the transfer with an I2C stop * condition. * - I2C_REP_START end the transfer with an I2C * repeated start condition. * - I2C_CONTINUE allow additional transferContinue() * calls. * . * @return true for success else false. */ bool I2cMasterBase::transferContinue(void *buf, size_t nbytes, uint8_t option) { uint8_t* p = reinterpret_cast(buf); if (_state == STATE_RX_DATA) { for (size_t i = 0; i < nbytes; i++) { p[i] = read(i == (nbytes - 1) && option != I2C_CONTINUE); } } else if (_state == STATE_TX_DATA) { for (size_t i = 0; i < nbytes; i++) { if (!write(p[i])) { _state = STATE_TX_DATA_NACK; return false; } } } else { return false; } if (option == I2C_STOP) { stop(); _state = STATE_STOP; } else if (option == I2C_REP_START) { start(); _state = STATE_STOP; } return true; } //============================================================================== // WARNING don't change SoftI2cMaster unless you verify the change with a scope //------------------------------------------------------------------------------ /** * Constructor, initialize SCL/SDA pins and set the bus high. * * @param[in] sdaPin The software SDA pin number. * * @param[in] sclPin The software SCL pin number. */ SoftI2cMaster::SoftI2cMaster(uint8_t sclPin, uint8_t sdaPin) { begin(sclPin, sdaPin); } //------------------------------------------------------------------------------ /** * Initialize SCL/SDA pins and set the bus high. * * @param[in] sdaPin The software SDA pin number. * * @param[in] sclPin The software SCL pin number. */ void SoftI2cMaster::begin(uint8_t sclPin, uint8_t sdaPin) { uint8_t port; // Get bit mask and address of scl registers. _sclBit = digitalPinToBitMask(sclPin); port = digitalPinToPort(sclPin); _sclDDR = portModeRegister(port); volatile uint8_t* sclOutReg = portOutputRegister(port); // Get bit mask and address of sda registers. _sdaBit = digitalPinToBitMask(sdaPin); port = digitalPinToPort(sdaPin); _sdaDDR = portModeRegister(port); _sdaInReg = portInputRegister(port); volatile uint8_t* sdaOutReg = portOutputRegister(port); // Clear PORT bit for scl and sda. uint8_t s = SREG; noInterrupts(); *sclOutReg &= ~_sclBit; *sdaOutReg &= ~_sdaBit; SREG = s; // Set scl and sda high. writeScl(HIGH); writeSda(HIGH); } //------------------------------------------------------------------------------ /* Read a byte and send ACK if more reads follow else NACK to terminate read. * * @param[in] last Set true to terminate the read else false. * * @return The byte read from the I2C bus. */ uint8_t SoftI2cMaster::read(uint8_t last) { uint8_t b = 0; // Set sda to high Z mode for read. writeSda(HIGH); // Read a byte. for (uint8_t i = 0; i < 8; i++) { // Don't change this loop unless you verify the change with a scope. b <<= 1; sclDelay(16); writeScl(HIGH); sclDelay(12); if (readSda()) { b |= 1; } writeScl(LOW); } // send ACK or NACK writeSda(last); sclDelay(12); writeScl(HIGH); sclDelay(18); writeScl(LOW); writeSda(LOW); return b; } //------------------------------------------------------------------------------ /* Issue a start condition. */ void SoftI2cMaster::start() { if (!readSda()) { writeSda(HIGH); writeScl(HIGH); sclDelay(20); } writeSda(LOW); sclDelay(20); writeScl(LOW); } //------------------------------------------------------------------------------ /* Issue a stop condition. */ void SoftI2cMaster::stop(void) { writeSda(LOW); sclDelay(20); writeScl(HIGH); sclDelay(20); writeSda(HIGH); sclDelay(20); } //------------------------------------------------------------------------------ /* * Write a byte. * * @param[in] data The byte to send. * * @return The value true, 1, if the slave returned an ACK or false for NACK. */ bool SoftI2cMaster::write(uint8_t data) { // write byte for (uint8_t m = 0X80; m != 0; m >>= 1) { // don't change this loop unless you verify the change with a scope writeSda(m & data); sclDelay(8); writeScl(HIGH); sclDelay(18); writeScl(LOW); } sclDelay(8); // Go to sda high Z mode for input. writeSda(HIGH); writeScl(HIGH); sclDelay(16); // Get ACK or NACK. uint8_t rtn = readSda(); // pull scl low. writeScl(LOW); // Pull sda low. writeSda(LOW); return rtn == 0; } /** @} */