diff --git a/libraries/I2C/BitBangI2C.cpp b/libraries/I2C/BitBangI2C.cpp index 214af934..00f959fa 100644 --- a/libraries/I2C/BitBangI2C.cpp +++ b/libraries/I2C/BitBangI2C.cpp @@ -45,6 +45,9 @@ BitBangI2C::BitBangI2C(uint8_t dataPin, uint8_t clockPin) : _dataPin(dataPin) , _clockPin(clockPin) , started(false) + , acked(true) + , inWrite(false) + , readCount(0) { // Initially set the CLOCK and DATA lines to be outputs in the high state. pinMode(_clockPin, OUTPUT); @@ -53,26 +56,11 @@ BitBangI2C::BitBangI2C(uint8_t dataPin, uint8_t clockPin) digitalWrite(_dataPin, HIGH); } -/** - * \typedef BitBangI2C::ack_t - * \brief Type that represents an I2C ACK or NACK indication. - */ +unsigned int BitBangI2C::maxTransferSize() const +{ + return 0xFFFF; +} -/** - * \var BitBangI2C::ACK - * \brief Indicates that an I2C operation was acknowledged. - */ - -/** - * \var BitBangI2C::NACK - * \brief Indicates that an I2C operation was not acknowledged. - */ - -/** - * \brief Transmits an I2C start condition on the bus. - * - * \sa startRead(), startWrite() - */ void BitBangI2C::start() { pinMode(_dataPin, OUTPUT); @@ -87,11 +75,9 @@ void BitBangI2C::start() digitalWrite(_clockPin, LOW); i2cDelay(); started = true; + acked = true; } -/** - * \brief Transmits an I2C stop condition on the bus. - */ void BitBangI2C::stop() { pinMode(_dataPin, OUTPUT); @@ -101,6 +87,7 @@ void BitBangI2C::stop() digitalWrite(_dataPin, HIGH); i2cDelay(); started = false; + inWrite = false; } #define I2C_WRITE 0x00 @@ -108,84 +95,77 @@ void BitBangI2C::stop() #define I2C_READ 0x01 #define I2C_READ10 0xF1 -/** - * \brief Starts a write operation by sending a start condition and the I2C control byte. - * - * The \a address must be the 7-bit or 10-bit address of the I2C slave - * on the bus. - * - * Returns BitBangI2C::ACK or BitBangI2C::NACK to indicate whether the - * read operation was acknowledged by the slave or not. - * - * \sa startWrite() - */ -BitBangI2C::ack_t BitBangI2C::startWrite(unsigned int address) +void BitBangI2C::startWrite(unsigned int address) { start(); + inWrite = true; if (address < 0x80) { // 7-bit address. - return write((uint8_t)((address << 1) | I2C_WRITE)); + write((uint8_t)((address << 1) | I2C_WRITE)); } else { // 10-bit address. - if (write((uint8_t)(((address >> 7) & 0x06)) | I2C_WRITE10) == NACK) - return NACK; - return write((uint8_t)address); + write((uint8_t)(((address >> 7) & 0x06)) | I2C_WRITE10); + write((uint8_t)address); } } -/** - * \brief Starts a read operation by sending the start condition and the I2C control byte. - * - * The \a address must be the 7-bit or 10-bit address of the I2C slave - * on the bus. - * - * Returns BitBangI2C::ACK or BitBangI2C::NACK to indicate whether the - * read operation was acknowledged by the slave or not. - * - * \sa startWrite() - */ -BitBangI2C::ack_t BitBangI2C::startRead(unsigned int address) -{ - start(); - if (address < 0x80) { - // 7-bit address. - return write((uint8_t)((address << 1) | I2C_READ)); - } else { - // 10-bit address. - if (write((uint8_t)(((address >> 7) & 0x06)) | I2C_READ10) == NACK) - return NACK; - return write((uint8_t)address); - } -} - -/** - * \brief Writes a single byte \a value on the I2C bus. - * - * Returns BitBangI2C::ACK or BitBangI2C::NACK to indicate whether the - * slave acknowledged the byte or not. - */ -BitBangI2C::ack_t BitBangI2C::write(uint8_t value) +void BitBangI2C::write(uint8_t value) { uint8_t mask = 0x80; while (mask != 0) { writeBit((value & mask) != 0); mask >>= 1; } - return readBit(); + if (readBit()) // 0: ACK, 1: NACK + acked = false; } -/** - * \brief Reads a single byte from the I2C bus. - * - * If \a ack is BitBangI2C::ACK, then the byte is acknowledged. Otherwise - * the byte will not be acknowledged. - */ -uint8_t BitBangI2C::read(ack_t ack) +bool BitBangI2C::endWrite() +{ + stop(); + return acked; +} + +bool BitBangI2C::startRead(unsigned int address, unsigned int count) +{ + start(); + inWrite = false; + if (address < 0x80) { + // 7-bit address. + write((uint8_t)((address << 1) | I2C_READ)); + } else { + // 10-bit address. + write((uint8_t)(((address >> 7) & 0x06)) | I2C_READ10); + write((uint8_t)address); + } + if (!acked) { + readCount = 0; + return false; + } + readCount = count; + return true; +} + +unsigned int BitBangI2C::available() +{ + return readCount; +} + +uint8_t BitBangI2C::read() { uint8_t value = 0; for (uint8_t bit = 0; bit < 8; ++bit) value = (value << 1) | readBit(); - writeBit(ack); + if (readCount > 1) { + // More bytes left to read - send an ACK. + writeBit(false); + --readCount; + } else { + // Last byte - send the NACK and a stop condition. + writeBit(true); + stop(); + readCount = 0; + } return value; } diff --git a/libraries/I2C/BitBangI2C.h b/libraries/I2C/BitBangI2C.h index da966332..aa0be8c5 100644 --- a/libraries/I2C/BitBangI2C.h +++ b/libraries/I2C/BitBangI2C.h @@ -23,31 +23,32 @@ #ifndef BitBangI2C_h #define BitBangI2C_h -#include +#include "I2CMaster.h" -class BitBangI2C { +class BitBangI2C : public I2CMaster { public: BitBangI2C(uint8_t dataPin, uint8_t clockPin); - typedef bool ack_t; + unsigned int maxTransferSize() const; - static const ack_t ACK = false; - static const ack_t NACK = true; + void startWrite(unsigned int address); + void write(uint8_t value); + bool endWrite(); - void start(); - void stop(); - - ack_t startWrite(unsigned int address); - ack_t startRead(unsigned int address); - - ack_t write(uint8_t value); - uint8_t read(ack_t ack = ACK); + bool startRead(unsigned int address, unsigned int count); + unsigned int available(); + uint8_t read(); private: uint8_t _dataPin; uint8_t _clockPin; bool started; + bool acked; + bool inWrite; + unsigned int readCount; + void start(); + void stop(); void writeBit(bool bit); bool readBit(); }; diff --git a/libraries/I2C/I2CMaster.cpp b/libraries/I2C/I2CMaster.cpp new file mode 100644 index 00000000..8b6dbf8d --- /dev/null +++ b/libraries/I2C/I2CMaster.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2012 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "I2CMaster.h" + +/** + * \class I2CMaster I2CMaster.h + * \brief Abstract base class for I2C master implementations. + * + * \sa I2CMaster + */ + +/** + * \fn unsigned int I2CMaster::maxTransferSize() const + * \brief Returns the maximum number of bytes that can be read or written in a single request by this bus master. + */ + +/** + * \fn void I2CMaster::startWrite(unsigned int address) + * \brief Starts a write operation by sending a start condition and the I2C control byte. + * + * The \a address must be the 7-bit or 10-bit address of the I2C slave + * on the bus. + * + * \sa write(), endWrite(), startRead() + */ + +/** + * \fn void I2CMaster::write(uint8_t value) + * \brief Writes a single byte \a value on the I2C bus. + * + * \sa startWrite(), endWrite() + */ + +/** + * \fn bool I2CMaster::endWrite() + * \brief Ends the current write operation. + * + * Returns true if the write operation was acknowledged; false otherwise. + * + * \sa startWrite(), write() + */ + +/** + * \fn bool I2CMaster::startRead(unsigned int address, unsigned int count) + * \brief Starts a read operation for \a count bytes by sending the start condition and the I2C control byte. + * + * The \a address must be the 7-bit or 10-bit address of the I2C slave + * on the bus. + * + * Returns true if the read request was acknowledged by the I2C slave + * or false otherwise. If true, this function should be followed by + * \a count calls to read() to fetch the bytes. + * + * \sa available(), read(), startWrite() + */ + +/** + * \fn unsigned int I2CMaster::available() + * \brief Returns the number of bytes that are still available for reading. + * + * \sa startRead(), read() + */ + +/** + * \fn uint8_t I2CMaster::read() + * \brief Reads a single byte from the I2C bus. + * + * \sa startRead(), available() + */ diff --git a/libraries/I2C/I2CMaster.h b/libraries/I2C/I2CMaster.h new file mode 100644 index 00000000..d5b28ac1 --- /dev/null +++ b/libraries/I2C/I2CMaster.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef I2CMaster_h +#define I2CMaster_h + +#include + +class I2CMaster { +public: + virtual unsigned int maxTransferSize() const = 0; + + virtual void startWrite(unsigned int address); + virtual void write(uint8_t value) = 0; + virtual bool endWrite() = 0; + + virtual bool startRead(unsigned int address, unsigned int count) = 0; + virtual unsigned int available() = 0; + virtual uint8_t read() = 0; +}; + +#endif diff --git a/libraries/RTC/DS1307RTC.cpp b/libraries/RTC/DS1307RTC.cpp index e59cb700..ab9c0f14 100644 --- a/libraries/RTC/DS1307RTC.cpp +++ b/libraries/RTC/DS1307RTC.cpp @@ -21,7 +21,7 @@ */ #include "DS1307RTC.h" -#include "../I2C/BitBangI2C.h" +#include "../I2C/I2CMaster.h" #include /** @@ -76,38 +76,29 @@ * * \sa hasUpdates() */ -DS1307RTC::DS1307RTC(BitBangI2C &bus, uint8_t oneHzPin) +DS1307RTC::DS1307RTC(I2CMaster &bus, uint8_t oneHzPin) : _bus(&bus) , _oneHzPin(oneHzPin) , prevOneHz(false) , _isRealTime(true) { // Make sure the CH bit in register 0 is off or the clock won't update. - if (_bus->startWrite(DS1307_I2C_ADDRESS) == BitBangI2C::ACK) { - _bus->write(DS1307_SECOND); - _bus->startRead(DS1307_I2C_ADDRESS); - uint8_t value = _bus->read(BitBangI2C::NACK); - _bus->stop(); - if ((value & 0x80) != 0) { - _bus->startWrite(DS1307_I2C_ADDRESS); - _bus->write(DS1307_SECOND); - _bus->write(value & 0x7F); - _bus->stop(); - } + _bus->startWrite(DS1307_I2C_ADDRESS); + _bus->write(DS1307_SECOND); + if (_bus->startRead(DS1307_I2C_ADDRESS, 1)) { + uint8_t value = _bus->read(); + if ((value & 0x80) != 0) + writeRegister(DS1307_SECOND, value & 0x7F); } else { // Did not get an acknowledgement from the RTC chip. _isRealTime = false; - _bus->stop(); } // Turn on the 1 Hz square wave signal if required. if (oneHzPin != 255 && _isRealTime) { pinMode(oneHzPin, INPUT); digitalWrite(oneHzPin, HIGH); - _bus->startWrite(DS1307_I2C_ADDRESS); - _bus->write(DS1307_CONTROL); - _bus->write(0x10); - _bus->stop(); + writeRegister(DS1307_CONTROL, 0x10); } // Initialize the alarms in the RTC chip's NVRAM. @@ -168,11 +159,16 @@ void DS1307RTC::readTime(RTCTime *value) if (_isRealTime) { _bus->startWrite(DS1307_I2C_ADDRESS); _bus->write(DS1307_SECOND); - _bus->startRead(DS1307_I2C_ADDRESS); - value->second = fromBCD(_bus->read() & 0x7F); - value->minute = fromBCD(_bus->read()); - value->hour = fromHourBCD(_bus->read(BitBangI2C::NACK)); - _bus->stop(); + if (_bus->startRead(DS1307_I2C_ADDRESS, 3)) { + value->second = fromBCD(_bus->read() & 0x7F); + value->minute = fromBCD(_bus->read()); + value->hour = fromHourBCD(_bus->read()); + } else { + // RTC chip is not responding. + value->second = 0; + value->minute = 0; + value->hour = 0; + } } else { RTC::readTime(value); } @@ -186,11 +182,16 @@ void DS1307RTC::readDate(RTCDate *value) } _bus->startWrite(DS1307_I2C_ADDRESS); _bus->write(DS1307_DATE); - _bus->startRead(DS1307_I2C_ADDRESS); - value->day = fromBCD(_bus->read()); - value->month = fromBCD(_bus->read()); - value->year = fromBCD(_bus->read(BitBangI2C::NACK)) + 2000; - _bus->stop(); + if (_bus->startRead(DS1307_I2C_ADDRESS, 3)) { + value->day = fromBCD(_bus->read()); + value->month = fromBCD(_bus->read()); + value->year = fromBCD(_bus->read()) + 2000; + } else { + // RTC chip is not responding. + value->day = 1; + value->month = 1; + value->year = 2012; + } } inline uint8_t toBCD(uint8_t value) @@ -206,7 +207,7 @@ void DS1307RTC::writeTime(const RTCTime *value) _bus->write(toBCD(value->second)); _bus->write(toBCD(value->minute)); _bus->write(toBCD(value->hour)); // Changes mode to 24-hour clock. - _bus->stop(); + _bus->endWrite(); } else { RTC::writeTime(value); } @@ -220,7 +221,7 @@ void DS1307RTC::writeDate(const RTCDate *value) _bus->write(toBCD(value->day)); _bus->write(toBCD(value->month)); _bus->write(toBCD(value->year % 100)); - _bus->stop(); + _bus->endWrite(); } else { RTC::writeDate(value); } @@ -231,11 +232,16 @@ void DS1307RTC::readAlarm(uint8_t alarmNum, RTCAlarm *value) if (_isRealTime) { _bus->startWrite(DS1307_I2C_ADDRESS); _bus->write(DS1307_ALARMS + alarmNum * DS1307_ALARM_SIZE); - _bus->startRead(DS1307_I2C_ADDRESS); - value->hour = fromBCD(_bus->read()); - value->minute = fromBCD(_bus->read()); - value->flags = _bus->read(BitBangI2C::NACK); - _bus->stop(); + if (_bus->startRead(DS1307_I2C_ADDRESS, 3)) { + value->hour = fromBCD(_bus->read()); + value->minute = fromBCD(_bus->read()); + value->flags = _bus->read(); + } else { + // RTC chip is not responding. + value->hour = 0; + value->minute = 0; + value->flags = 0; + } } else { RTC::readAlarm(alarmNum, value); } @@ -249,7 +255,7 @@ void DS1307RTC::writeAlarm(uint8_t alarmNum, const RTCAlarm *value) _bus->write(toBCD(value->hour)); _bus->write(toBCD(value->minute)); _bus->write(value->flags); - _bus->stop(); + _bus->endWrite(); } else { RTC::writeAlarm(alarmNum, value); } @@ -257,37 +263,23 @@ void DS1307RTC::writeAlarm(uint8_t alarmNum, const RTCAlarm *value) uint8_t DS1307RTC::readByte(uint8_t offset) { - if (_isRealTime) { - _bus->startWrite(DS1307_I2C_ADDRESS); - _bus->write(DS1307_NVRAM + offset); - _bus->startRead(DS1307_I2C_ADDRESS); - uint8_t value = _bus->read(BitBangI2C::NACK); - _bus->stop(); - return value; - } else { + if (_isRealTime) + return readRegister(DS1307_NVRAM + offset); + else return RTC::readByte(offset); - } } void DS1307RTC::writeByte(uint8_t offset, uint8_t value) { - if (_isRealTime) { - _bus->startWrite(DS1307_I2C_ADDRESS); - _bus->write(DS1307_NVRAM + offset); - _bus->write(value); - _bus->stop(); - } else { + if (_isRealTime) + writeRegister(DS1307_NVRAM + offset, value); + else RTC::writeByte(offset, value); - } } void DS1307RTC::initAlarms() { - _bus->startWrite(DS1307_I2C_ADDRESS); - _bus->write(DS1307_ALARM_MAGIC); - _bus->startRead(DS1307_I2C_ADDRESS); - uint8_t value = _bus->read(BitBangI2C::NACK); - _bus->stop(); + uint8_t value = readRegister(DS1307_ALARM_MAGIC); if (value != (0xB0 + ALARM_COUNT)) { // This is the first time we have used this clock chip, // so initialize all alarms to their default state. @@ -297,10 +289,7 @@ void DS1307RTC::initAlarms() alarm.flags = 0; for (uint8_t index = 0; index < ALARM_COUNT; ++index) writeAlarm(index, &alarm); - _bus->startWrite(DS1307_I2C_ADDRESS); - _bus->write(DS1307_ALARM_MAGIC); - _bus->write(0xB0 + ALARM_COUNT); - _bus->stop(); + writeRegister(DS1307_I2C_ADDRESS, 0xB0 + ALARM_COUNT); // Also clear the rest of NVRAM so that it is in a known state. // Otherwise we'll have whatever garbage was present at power-on. @@ -308,6 +297,23 @@ void DS1307RTC::initAlarms() _bus->write(DS1307_NVRAM); for (uint8_t index = DS1307_NVRAM; index < DS1307_ALARMS; ++index) _bus->write(0); - _bus->stop(); + _bus->endWrite(); } } + +uint8_t DS1307RTC::readRegister(uint8_t reg) +{ + _bus->startWrite(DS1307_I2C_ADDRESS); + _bus->write(reg); + if (!_bus->startRead(DS1307_I2C_ADDRESS, 1)) + return 0; // RTC chip is not responding. + return _bus->read(); +} + +bool DS1307RTC::writeRegister(uint8_t reg, uint8_t value) +{ + _bus->startWrite(DS1307_I2C_ADDRESS); + _bus->write(reg); + _bus->write(value); + return _bus->endWrite(); +} diff --git a/libraries/RTC/DS1307RTC.h b/libraries/RTC/DS1307RTC.h index 25a2ea78..2ef08922 100644 --- a/libraries/RTC/DS1307RTC.h +++ b/libraries/RTC/DS1307RTC.h @@ -25,11 +25,11 @@ #include "RTC.h" -class BitBangI2C; +class I2CMaster; class DS1307RTC : public RTC { public: - DS1307RTC(BitBangI2C &bus, uint8_t oneHzPin = 255); + DS1307RTC(I2CMaster &bus, uint8_t oneHzPin = 255); ~DS1307RTC(); bool isRealTime() const { return _isRealTime; } @@ -49,12 +49,15 @@ public: void writeByte(uint8_t offset, uint8_t value); private: - BitBangI2C *_bus; + I2CMaster *_bus; uint8_t _oneHzPin; bool prevOneHz; bool _isRealTime; void initAlarms(); + + uint8_t readRegister(uint8_t reg); + bool writeRegister(uint8_t reg, uint8_t value); }; #endif