// Copyright 2010 Olivier Gillet. // // Author: Olivier Gillet (ol.gillet@gmail.com) // // This program 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 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 General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // ----------------------------------------------------------------------------- // // Implementation of the I2C protocol (Master mode only for now). // // Note that this file is not in the hal directory directly because I don't want // the interrupt handler code for TWI to be linked with every project. #ifndef AVRLIB_I2C_I2C_H_ #define AVRLIB_I2C_I2C_H_ #include #include "avrlib/gpio.h" #include "avrlib/avrlib.h" #include "avrlib/ring_buffer.h" namespace avrlib { IORegister(TWCR); IORegister(TWSR); typedef BitInRegister Prescaler0; typedef BitInRegister Prescaler1; typedef BitInRegister I2cEnable; typedef BitInRegister I2cInterrupt; typedef BitInRegister I2cAck; typedef BitInRegister I2cStart; typedef BitInRegister I2cStop; enum I2cState { I2C_STATE_READY, I2C_STATE_TRANSMITTING, I2C_STATE_RECEIVING, }; enum I2cError { I2C_ERROR_NONE = 0xff, I2C_ERROR_NO_ACK_FOR_ADDRESS = 0x01, I2C_ERROR_NO_ACK_FOR_DATA = TW_MT_SLA_NACK, I2C_ERROR_ARBITRATION_LOST = TW_MT_DATA_NACK, I2C_ERROR_BUS_ERROR = 0xfe, I2C_ERROR_TIMEOUT = 0x02, }; template class I2cOutput { public: I2cOutput() { } enum { buffer_size = output_buffer_size, data_size = 8 }; typedef typename DataTypeForSize::Type Value; }; template class I2cInput { public: I2cInput() { } enum { buffer_size = input_buffer_size, data_size = 8 }; typedef typename DataTypeForSize::Type Value; }; // I2C Handler. extern void (*i2c_handler_)(); template class I2cMaster { public: I2cMaster() { } typedef typename DataTypeForSize::data_size>::Type Value; static void Init() { // Prescaler is set to a factor of 1. Prescaler0::clear(); Prescaler1::clear(); TWBR = (F_CPU / frequency - 16) / 2; I2cEnable::set(); I2cInterrupt::set(); I2cAck::set(); state_ = I2C_STATE_READY; i2c_handler_ = &Handler; } static void Done() { I2cInterrupt::clear(); I2cEnable::clear(); I2cAck::clear(); i2c_handler_ = NULL; } static uint8_t Wait() { while (state_ != I2C_STATE_READY) { } return error_; } static uint8_t Wait(uint16_t num_cycles) { while (state_ != I2C_STATE_READY && num_cycles) { --num_cycles; } if (!num_cycles) { error_ = I2C_ERROR_TIMEOUT; } return error_; } static uint8_t Send(uint8_t address) { // The output buffer is empty, no need to do anything. if (!Output::readable()) { return 0; } // Sorry, data can be sent only when the line is not busy. if (state_ != I2C_STATE_READY) { return 0; } error_ = I2C_ERROR_NONE; state_ = I2C_STATE_RECEIVING; slarw_ = (address << 1) | TW_WRITE; uint8_t size = Output::readable(); I2cStart::set(); return size; } static uint8_t Request(uint8_t address, uint8_t requested) { // Make sure that we don't request more than the buffer can hold. if (requested >= Input::writable()) { requested = Input::writable() - 1; } // Sorry, data can be requested only when the line is not busy. if (state_ != I2C_STATE_READY) { return 0; } error_ = I2C_ERROR_NONE; state_ = I2C_STATE_RECEIVING; slarw_ = (address << 1) | TW_READ; received_ = 0; requested_ = requested; I2cStart::set(); return requested; } // All the read/write operations are done on the buffer, so they do not // block. static inline void Write(Value v) { Output::Write(v); } static inline uint8_t writable() { return Output::writable(); } static inline uint8_t NonBlockingWrite(Value v) { return Output::NonBlockingWrite(v); } static inline void Overwrite(Value v) { Output::Overwrite(v); } static inline Value Read() { return Input::Read(); } static inline uint8_t readable() { return Input::readable(); } static inline int16_t NonBlockingRead() { return Input::NonBlockingRead(); } static inline Value ImmediateRead() { return Input::ImmediateRead(); } static inline void FlushInputBuffer() { Input::Flush(); } static inline void FlushOutputBuffer() { Output::Flush(); } private: static inline void Continue(uint8_t ack) { if (ack) { TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA); } else { TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT); } } static inline void Stop() { I2cStop::set(); while (I2cStop::value()) { } state_ = I2C_STATE_READY; } static inline void Abort() { TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT); state_ = I2C_STATE_READY; } static void Handler() { switch (TW_STATUS) { case TW_START: case TW_REP_START: TWDR = slarw_; Continue(1); break; case TW_MT_DATA_ACK: case TW_MT_SLA_ACK: if (Output::readable()) { TWDR = Output::ImmediateRead(); Continue(1); } else { Stop(); } break; case TW_MT_SLA_NACK: case TW_MT_DATA_NACK: error_ = TW_STATUS; Stop(); break; case TW_MT_ARB_LOST: error_ = I2C_ERROR_ARBITRATION_LOST; Abort(); break; case TW_MR_DATA_ACK: Input::Overwrite(TWDR); ++received_; case TW_MR_SLA_ACK: if (received_ < requested_) { Continue(1); } else { Continue(0); } break; case TW_MR_DATA_NACK: Input::Overwrite(TWDR); ++received_; case TW_MR_SLA_NACK: Stop(); break; case TW_NO_INFO: break; case TW_BUS_ERROR: error_ = I2C_ERROR_BUS_ERROR; Stop(); break; } } public: typedef RingBuffer > Input; typedef RingBuffer > Output; private: static volatile uint8_t state_; static volatile uint8_t error_; static volatile uint8_t slarw_; static volatile uint8_t received_; static uint8_t requested_; DISALLOW_COPY_AND_ASSIGN(I2cMaster); }; /* static */ template volatile uint8_t I2cMaster::state_; /* static */ template volatile uint8_t I2cMaster::error_; /* static */ template volatile uint8_t I2cMaster::slarw_; /* static */ template volatile uint8_t I2cMaster::received_; /* static */ template uint8_t I2cMaster::requested_; } // namespace avrlib #endif // AVRLIB_I2C_I2C_H_