Go Down

Topic: Expert Comment Needed on a New I2C Library API (Read 4 times) previous topic - next topic

fat16lib

I am writing a new library for the AVR Two Wire Interface.  The Wire library is not very RTOS friendly.

The library is for master mode.  I plan to write a separate slave mode library later.

This new library is designed to be used with various RTOSs and the calling thread will sleep while the I2C transfer occurs.

Is this API adequate for slave devices that may be used with Arduino?

The class API has three functions (detailed documentation follows later):
Quote

Public Member Functions

void  begin (bool speed=I2C_400KHZ, bool pullups=I2C_NO_PULLUPS);
bool  transfer (uint8_t addrRW, void *buf, size_t nbytes, uint8_t option=I2C_STOP);
bool  transferContinue (void *buf, size_t nbytes, uint8_t option=I2C_STOP);

Here is an example for a DS1307 RTC chip that reads the DS1307 registers and prints the data and time.
Code: [Select]

#include <NilTwi.h>

// Two Wire Interface instance.
NilTwiMaster twi;

// DS1307 I2C address in high bits, R/W in low bit.
const uint8_t DS1307ADDR = 0XD0;
// DS1307 address and read bit.
const uint8_t RTC_READ = DS1307ADDR | I2C_READ;
// DS1307 address and write bit
const uint8_t RTC_WRITE = DS1307ADDR | I2C_WRITE;
//------------------------------------------------------------------------------
void setup() {
  Serial.begin(9600);
 
  // Use standard 100 kHz speed and no internal pullups.
  twi.begin(I2C_100KHZ);
}
//------------------------------------------------------------------------------
void loop() {
  uint8_t add = 0;   // DS1307 time/date register address.
  uint8_t buf[7];    // Buffer for DS1307 register values.
 
  // Write DS1307 register address. 
  // Demo of option argument, repeat start is not required here.
  twi.transfer(RTC_WRITE, &add, 1, I2C_REP_START);
 
  // Read DS1307 time/date registers.
  twi.transfer(RTC_READ, buf, 7);

  // Print YYYY-MM-DD hh:mm:ss
  Serial.print("20");
  for (int i = 6; i >= 0; i--){
    // Skip day of week.
    if (i == 3) continue;
   
    // Always print two digits
    if (buf[i] < 10) Serial.print('0');
   
    // Ds1307 is BCD.
    Serial.print(buf[i], HEX);
   
    // Print field separator.
    if (i == 6 || i == 5) Serial.print('-');
    if (i == 4) Serial.print(' ');
    if (i == 2 || i == 1) Serial.print(':');

  }
  Serial.println();
  delay(1000);
}

Here is more detailed documentation for the class members.
Quote

void TwiMaster::begin(bool speed = I2C_400KHZ, bool pullups = I2C_NO_PULLUPS)

  Initialize the AVR 2-wire Serial Interface.

  Parameters:
    [in]  speed  I2C bus speed, one of:

        I2C_400KHZ
        I2C_100KHZ

    [in]  pullups  State of internal AVR pullups, one of:

        I2C_NO_PULLUPS
        I2C_PULLUPS


bool TwiMaster::transfer(uint8_t addrRW, void* buf,
                         size_t nbytes, uint8_t option = I2C_STOP)

  Start an I2C transfer with possible continuation.

  Parameters:
    [in]      addrRW  I2C slave address plus R/W bit.
    [in,out]  buf     Source or destination data address for transfer.
    [in]      nbytes  Number of bytes to transfer (may be zero).
    [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 repeat start condition.
        I2C_CONTINUE   allow additional transferContinue() calls.

  Returns:
    true for success else false.


bool TwiMaster::transferContinue(void* buf, size_t nbytes,
                                    uint8_t option = I2C_STOP)

  Continue an I2C transfer.

  Parameters:
    [in,out] buf     Source or destination data address for transfer.
    [in]     nbytes  Number of bytes to transfer (may be zero).
    [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 repeat start condition.
        I2C_CONTINUE   allow additional transferContinue() calls.

  Returns:
    true for success else false.

robtillaart


bool transfer(...)

The bool indicates success or failure.

Is there a more detailed error available?
In the current implementation there are a few (lowlevel) error codes that indicate why communication failed. I find them helpful for debugging.
/*
* Function twi_writeTo
...
* Output   0 .. success
*          1 .. length to long for buffer
*          2 .. address send, NACK received
*          3 .. data send, NACK received
*          4 .. other twi error (lost bus arbitration, bus error, ..)
*/

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

#2
Feb 09, 2013, 10:46 pm Last Edit: Feb 09, 2013, 10:49 pm by robtillaart Reason: 1
idea:
to solve address issues it might be useful to have a I2C scanner equivalent in the core of the new lib? (master only?)

Code: [Select]
// both return -1 if none found
int TwiMaster::scanGetFirst();  
int TwiMaster::scanGetNext();

// bit like this
int addr = twi.scanGetFirst();
while (addr > -1)
{
 Serial.println(addr);
 addr = twi.scanGetNext();
}
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Some getter functions? have no direct use for them, but makes it complete
bool getPullUps();
int getSPeed();

Question pops up?
How would the new lib communicate with an I2C EEPROM?   (esp eeprom address handling, page boundaries ...)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

fat16lib

Let me try to handle some of your comments.
Quote

    1 .. length to long for buffer

This can't happen since I don't copy the data to/from an internal buffer.  I just setup the address and count in static variables and the ISR moves data directly to/from the user's buffer.

Quote

*          2 .. address send, NACK received
*          3 .. data send, NACK received
*          4 .. other twi error (lost bus arbitration, bus error, ..)

The ISR saves the TWI state on entrance so the you can get the initial and current state of the bus.  Here are the possible codes:
Quote

// General TWI Master state codes
#define TWI_START                  0x08  // START has been transmitted
#define TWI_REP_START              0x10  // Repeated START has been transmitted
#define TWI_ARB_LOST               0x38  // Arbitration lost

// TWI Master Transmitter state codes
#define TWI_MTX_ADR_ACK            0x18  // SLA+W has been transmitted and ACK received
#define TWI_MTX_ADR_NACK           0x20  // SLA+W has been transmitted and NACK received
#define TWI_MTX_DATA_ACK           0x28  // Data byte has been transmitted and ACK received
#define TWI_MTX_DATA_NACK          0x30  // Data byte has been transmitted and NACK received

// TWI Master Receiver state codes
#define TWI_MRX_ADR_ACK            0x40  // SLA+R has been transmitted and ACK received
#define TWI_MRX_ADR_NACK           0x48  // SLA+R has been transmitted and NACK received
#define TWI_MRX_DATA_ACK           0x50  // Data byte has been received and ACK transmitted
#define TWI_MRX_DATA_NACK          0x58  // Data byte has been received and NACK transmitted

// TWI Miscellaneous state codes
#define TWI_NO_STATE               0xF8  // No relevant state information available; TWINT = "0"
#define TWI_BUS_ERROR              0x00  // Bus error due to an illegal START or STOP condition


For example if you call transferContinue() and it fails you can check the bus state.

Quote

idea:
to solve address issues it might be useful to have a I2C scanner equivalent in the core of the new lib? (master only?)


I am not sure exactly what you mean but the main reason that transfer() will allows zero length is to probe the bus.

On my DS1307 test setup  this sketch:
Code: [Select]

#include <NilTwi.h>

// Two Wire Interface instance.
NilTwiMaster twi;

void setup() {
  Serial.begin(9600);
  twi.begin();

  uint8_t add = 0;

  // try read
  do {
    if (twi.transfer(add | I2C_READ, 0, 0)) {
      Serial.print("Add read: 0X");
      Serial.println(add, HEX);
    }
    add += 2;
  }
  while (add);

  // try write
  add = 0;
  do {
    if (twi.transfer(add | I2C_WRITE, 0, 0)) {
      Serial.print("Add write: 0X");
      Serial.println(add, HEX);
    }
    add += 2;
  }
  while (add);
  Serial.println("Done"); 
}
void loop() {
}

Prints this:
Quote

Add read: 0XD0
Add write: 0XD0
Done


Quote

Some getter functions? have no direct use for them, but makes it complete
bool getPullUps();
int getSPeed();


Unfortunately I set the pull-ups and baud rate in begin() but don't save it.  I guess I could.
Quote

Question pops up?
How would the new lib communicate with an I2C EEPROM?   (esp eeprom address handling, page boundaries ...)

The user has total control of the transfer size.  I don't have internal buffers. 

You could send all day without a stop or repeat start by calling transferContinue().  There might be a time gap.

I didn't demo transferContinue() in my example. Here is a DS1307 write function:
Code: [Select]

  bool rtcWrite(uint8_t regAdd, uint8_t* buf, uint8_t count) {
  // Set place to write in DS1307 and don't end transfer.
  if (!twi.transfer(RTC_WRITE, &regAdd, 1, I2C_CONTINUE)) return false;

  // Write DS1307 registers or memory.  Default is to end with a stop condition.
  return twi.transferContinue(buf, count);
}


Go Up