I gave up on that and wrote a simple i2c (master-only) library instead.
If anybody wants to move this to the wiki, be my guest.
/*
This code (and license) is based on Joerg Wunsch's twitest example.
*
-
- "THE BEER-WARE LICENSE" (because GNU sucks):
- Marco Grubert wrote this file. As long as you retain this notice you
- can do whatever you want with this stuff. If we meet some day, and
- you think this stuff is worth it, you can buy me a beer in return.
- ----------------------------------------------------------------------------*/
/* USAGE:
#include "i2c.h"
I2C::Init(); // call this in setup
I2C::SendTo( deviceAddr, deviceRegister, aByte ); // sends a single byte
// For receiving data provide a buffer pointer and the buffer size
I2C::Buffer receiveBuffer(4); // 4 bytes large
I2C::ReceiveFrom( deviceAddr, deviceRegister, receiveBuffer.mBuffer, receiveBuffer.mLength );
All functions return true on success. If you get a false instead, you can inspect the hi and low
byte of I2C::ErrorCode for more details. You could also recompile the code with DEBUG
defined to get callstacks printed to Serial out.
*/
#ifndef I2CMASTER_H
#define I2CMASTER_H
#include <avr/io.h>
#include <compat/twi.h>
#ifndef CPU_FREQ
#define CPU_FREQ 16000000L // Lillypad might have to redefine this to 8MHz instead of 16
#endif
#ifndef TWI_FREQ
#define TWI_FREQ 100000L
#endif
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
// #define DEBUG // enable to print out callstack over serial port
#ifdef DEBUG
#define DPRINT(a) Serial.println(a);
#else
#define DPRINT(a)
#endif
namespace I2C {
// just a simple buffer class. You don't have to use it if you supply your own buffer
class CBuffer {
public:
CBuffer() :
mBuffer(0), mLength(0) {
}
CBuffer(uint8_t bufLength)
{
mLength= bufLength;
mBuffer = (uint8_t*) calloc(bufLength, sizeof(uint8_t));
}
void Init(uint8_t bufLength)
{
mLength= bufLength;
mBuffer = (uint8_t*) calloc(bufLength, sizeof(uint8_t));
}
uint8_t* mBuffer;
uint8_t mLength;
};
// low byte == TW_STATUS
// hi byte == library status
uint16_t ErrorCode=0;
void Init()
{
// activate internal pull-ups for twi
// as per note from atmega8 manual pg167
sbi(PORTC, 4);
sbi(PORTC, 5);
// initialize twi prescaler and bit rate
cbi(TWSR, TWPS0);
cbi(TWSR, TWPS1);
// twi bit rate formula from atmega128 manual pg 204
// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
// note: TWBR should be 10 or higher for master mode
// It is 72 for a 16mhz Wiring board with 100kHz TWI
TWBR = ((CPU_FREQ / TWI_FREQ) - 16) / 2;
// enable twi module, acks, and twi interrupt
TWCR = _BV(TWEN) ;
}
bool _SendStart(bool bRepeat=false)
{
DPRINT("Start in");
TWCR = (1<<TWINT)|(1<<TWSTA)| (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
DPRINT("Start TWINT");
if (bRepeat)
return (TW_STATUS == TW_REP_START);
else
return (TW_STATUS == TW_START);
}
bool _SendAddr(uint8_t addr, bool bWriteMode)
{
DPRINT("SendAdd in");
if (bWriteMode)
TWDR = (addr << 1) | TW_WRITE;
else
TWDR = (addr << 1) | TW_READ;
TWCR = _BV(TWINT) | _BV(TWEN); // clear interrupt to start transmission
while ((TWCR & _BV(TWINT)) == 0) ; // wait for transmission
DPRINT("SendAdd TWINT");
if (bWriteMode)
return (TW_STATUS== TW_MT_SLA_ACK);
else
return (TW_STATUS== TW_MR_SLA_ACK);
}
bool _SendByte(uint8_t data)
{
DPRINT("SendByte in");
TWDR = data;
TWCR = _BV(TWINT) | _BV(TWEN); // clear interrupt to start transmission
while ((TWCR & _BV(TWINT)) == 0) ; // wait for transmission
DPRINT("SendByte out");
return (TW_STATUS== TW_MT_DATA_ACK);
}
bool _ReceiveBytes(uint8_t* buffer, uint8_t len)
{
DPRINT("RecvByte in");
int16_t length= len; // allow <0
for (uint8_t twcr = _BV(TWINT) | _BV(TWEN) | _BV(TWEA); length > 0; --length)
{
DPRINT("RecvByte read");
if (length == 1)
{
DPRINT("RecvByte req nack");
twcr = _BV(TWINT) | _BV(TWEN); // last byte? don't schedule ACK
}
TWCR = twcr;
while ((TWCR & _BV(TWINT)) == 0) ; // wait for transmission
const uint8_t twst= TW_STATUS;
switch (twst)
{
case TW_MR_DATA_NACK:
if (length!=1) {
DPRINT("RecvByte Nack mismatch");
return false; // we sent NACK too early
}
case TW_MR_DATA_ACK:
DPRINT("RecvByte sent ack/nack");
*buffer++ = TWDR;
break;
default:
DPRINT("RecvByte error out");
return false;
}
}
DPRINT("RecvByte out");
return true;
}
bool _SendStop()
{
DPRINT("Sendstop");
TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); // send stop condition
return true;
}
class AutoStopper { // call I2C Stop when this object goes out of scope
public:
~AutoStopper() {
_SendStop();
}
};
#define TRY(a, b) { if (! a ) { ErrorCode=TW_STATUS | (b); return false; } }
bool ReceiveFrom(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_t buffLength)
{
DPRINT("Receivefrom in");
// first write register pointer
TRY( _SendStart(false), 1 );
{
AutoStopper stopper; // call stop when this object goes out of scope
TRY( _SendAddr(addr, true), 2);
TRY( _SendByte(reg), 3 );
// now request data from device with repeated start
TRY( _SendStart(true), 4 );
TRY( _SendAddr(addr, false), 5 );
TRY( _ReceiveBytes( buffer, buffLength ), 6 );
}
return true;
}
bool SendTo(uint8_t addr, uint8_t reg, uint8_t *buffer, uint8_t buffLength)
{
DPRINT("Sendto in");
TRY( _SendStart(false), 1 );
{
AutoStopper stopper; // call stop when this object goes out of scope
TRY( _SendAddr(addr, true), 2);
TRY( _SendByte(reg), 3 );
while (buffLength>=1) {
TRY( _SendByte(*buffer++), 4 );
--buffLength;
}
}
return true;
}
bool SendTo(uint8_t addr, uint8_t reg, uint8_t data)
{
return SendTo(addr, reg, &data, 1);
}
} // namespace I2C
#endif // I2CMASTER_H