mirror of
https://github.com/mysensors/MySensors.git
synced 2026-02-20 01:21:27 +01:00
250 lines
7.1 KiB
C++
250 lines
7.1 KiB
C++
/* 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
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
/**
|
|
* @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<uint8_t*>(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;
|
|
}
|
|
/** @} */
|