I2C Continuous writing (TWI changes)

Hi,

For a sensor I'm interfacing with, I am required to consistently send bytes to the sensor as fast as I2C can handle. This stops me from using Wire.endTransmission, since the overhead from the addressing would be too great. This is why I want to create a new function in the I2C library that allows for writing to the sensor, without a start (repeated or normal), address and stop.

I am having some issues with the required changes to the TWI library. I want to add a function similar to twi_writeTo, but without the aforementioned overhead. However, changing the ISR is proving difficult since for some of the variables used there, like the state names and such, I can't find where they are defined or changed. Some of the TWCR bits are not completely clear to me as well.

Does anyone know how this could be achieved, or if someone has created this functionality before?

Thanks in advance,
Ward

What's wrong with using the existing Wire.write(byte) function? It appears to do exactly what you want to do: write data to the sensor as fast as I2C can handle (at the speed you select, the default of which is far lower than the maximum the I2C bus is designed to do).

Unless your sensor does not follow the I2C standard, you will have to first do a Wire.beginTransmission(address) call so the sensor knows you're trying to write data to it.

Wire.write does not actually write any data, it merely sends data to the buffer (which by default is only 32 bytes). The endTransmission function has to be called in order to write the buffered data to the peripheral.

Mmm, interesting, I never knew of that limitation. I also wonder why that is, I thought it's perfectly fine for the master to pause transmission (don't send clock pulses) for a short while.

Looking at the Wire implementation you can send unlimited data if in slave mode using the twi_transmit(data, quantity) function. You'll have to look at the detailed implementation of those functions and the ATmega datasheet and the I2C specifications to understand how this could work.

By the way, what sensor are you working with that needs to receive data continuously? Even more so, why would you want to use the I2C bus for that, which is meant to be shared between devices?

What sensor are you using that needs that many fast cycle?

You can use register level codes that involve TWCR, TWDR, TWSR, TWBR, and TWAR.

I am currently looking at a SoftI2C library (GitHub - yasir-shahzad/SoftI2C: Software-based I2C communication library for Arduino), that allows for more freedom in what you want to send, I think this will work out. If this doesn't work out, I'll try the slave mode, thanks for the suggestion!

I don't know if I am able to share any specifics about the sensor, but a very fast continuous sending of data is required for proper operation. Using I2C is something that is out of my control, it is just what the sensor uses.

Yes, I tried that, but I got lost trying to understand the ISR, since it's not clear to me how exactly it operates. I just got stuck in loops everytime.

You can't
I2C requires a START and a STOP.

Is the sensor a commercially available product, or something proprietary?

Seems really odd that it needs continuous data sent to it, how do you even know when to get data back from it, much less actually have time to stop the data send and do a data read? Or is this just a case where the sensor needs a clock signal, and you are providing that as a by-product of the I2C communications?

Of course I will send a START and address at first, and my final message will end with a STOP, but everything in between has to be just data.

It is a commercially available sensor that I am using in proprietary ways together with the company that designed it. All the I2C bus is used for, is sending this continuous signal to the sensor, reading data from the sensor will happen in a different way.

If it's a 328, you can try these

//TWBR – TWI Bit Rate Register
//TWCR – TWI Control Register
//TWSR – TWI Status Register
//TWDR – TWI Data Register

/* For Slave only
TWAR – TWI (Slave) Address Register
TWAMR – TWI (Slave) Address Mask Register
*/

#define START 0x08
#define RSTART 0x10
#define SLA_ACK 0x18
#define SLA_NACK 0x20
#define DATA_ACK 0x28
#define DATA_NACK 0x30
#define NOT_BUSY 0xF8
#define TWI_ERROR 0x00

void Init_I2C(void)
{
  // Init TWI Interface
  TWSR = 0x00; // Prescaler = 1
  TWBR = 0x0C; // 400kHz
  TWBR = 0x48; // 100kHz
  TWCR = (1<<TWEN); // Enable TWI
}

int Send_START(void)
{
  // Send START condition
  TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
  //Wait for START to be sent
  while (!(TWCR & (1<<TWINT)));
  // Check if START was sent
  if ((TWSR & 0xF8) != START) return 1;
  return 0;
}

int Send_Addr(byte SLA_W)
{
  // Load Slave Address
  TWDR = SLA_W;
  // Send Address
  TWCR = (1<<TWINT) | (1<<TWEN); 
  // Wait for Address to be sent
  while (!(TWCR & (1<<TWINT)));
  // Check if Address was sent
  if ((TWSR & 0xF8) != SLA_ACK) return 1; 
  return 0;
}

int Send_Data(byte DATA)
{
  // Load Data
  TWDR = DATA;
  // Send Data 
  TWCR = (1<<TWINT) | (1<<TWEN);
  // Wait for Data to be sent
  while (!(TWCR & (1<<TWINT)));
  // Check if Data was sent
  if ((TWSR & 0xF8) != DATA_ACK) return 1;
  return 0;
}

void Send_STOP(void)
{
  // Send a STOP condition if done
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
}

1 Like

Thanks! This looks a lot like the library I will be trying out

It's actually documented in the ATmega328P datasheet.

TWI Bus is a byte oriented and interrupt driven protocol. Whenever the Slave receives its address or a data byte from the Master, it is immediately interrupted and goes to the ISR() to read the arrived address/data from TWDR Register.

Here is an example sketch (Register Level Codes) tested on UNO-NANO. In this example, the Master makes a Roll Call to the Slave at its address (0010011); the Slave receives the address and shows on its Serial Monitor. The attached file may worth reading.
I2C Master Sketch (UNO):

void setup()
{
  Serial.begin(9600);
  //-----TWI Bus and 100 kBit Rate)----------------
  bitSet(TWCR, TWEN);      //LH → TWEN-bit of TWCR
  bitSet(TWSR, TWPS1);    //TWSR1 = 1 ; speed selection
  bitClear(TWSR, TWPS0);   //TWSR0 = 0
  TWBR = 36;      //100 kbit/s   TWI Bus speed
  //--Master brings START condition on the TWI Bus:
  TWCR = 0b10100100;  //TWI bus formation; TWINT-bit is cleared; START condition is asserted
  //TWCR = TWINT TWEA TWSTA TWSTO TWWC TWEN X TWIE
  //Execution order: TWEN, TWINT, TWSTA, ...
  while (bitRead(TWCR, TWINT) != HIGH) //checking if process is done by looking LH for TWINT-bit
  {
    ;                 //wait until TWINT-bit becomes LH
  }
  Serial.println((TWSR & 0b11111000), HEX);   //shows: 08/correct
  //-----Roll calling of Slave at Slave Address: 0010011
  TWDR = 0b00100110;          //slaveAddres + Write-bit
  TWCR = 0b10000100;  //TWI bus remains enabled; TWINT-bit is cleared; START bit is OFF
  //TWCR = TWINT TWEA TWSTA TWSTO TWWC TWEN X TWIE
  //Execution order: TWEN, TWINT, TWSTA, ...
  while(bitRead(TWCR, TWINT) != HIGH)   //checking if process is completed
  {
    ;             //wait until the process is completed
  }
  Serial.print((TWSR & 0b11111000), HEX); //shows: 18; Slave recognized address
}

void loop()
{

}

I2C Slave Sketch (NANO):

byte recvData;
bool flag = false;;

void setup()
{
  Serial.begin(9600);
  //-----TWI Bus and 100 kBit Rate)----------------
  bitSet(TWCR, TWEN);      //LH → TWEN-bit of TWCR
  bitSet(TWCR, TWEA);  //Enable ACK Bit; see data sheets
  // bitSet(TWSR, TWPS1);    //TWSR1 = 1 ; speed selection
  // bitClear(TWSR, TWPS0);   //TWSR0 = 0
  // TWBR = 36;      //100 kbit/s   TWI Bus speed
  //--setting 7-bit Slave address as: 0010011--
  TWAR = 0b00100110;
  //--- Interrupt logic enabled
  bitSet(TWCR, TWIE); //Local interrupt enable bit is active
  bitSet(SREG, 7);  //Global interrupt enable bit is active
}

void loop()
{
  if (flag == true)
  {
    Serial.println(recvData, BIN);
    flag = false;
  }
}

ISR(TWI_vect)
{
  flag = true;
  recvData = TWDR;
  bitSet(TWCR, TWINT); //clear TWINT flag by placing HIGH at this (TWINT) bit
}

I2CRegisterLevelCodes (1).pdf (374.1 KB)

1 Like

Can you give a START condition and then endlessly send data ? A data byte is only 9 clock pulses and 8 databits.
At 100kHz, the overhead is only little. Have you tried 400kHz and higher ? The standard I2C bus is maximum 400kHz, but some chips can go up to 1MHz. If you use the Wire.setClock() on a Arduino Uno, then you have to verify the signals with a Logic Analyzer to see if it is set to that frequency. I think that 50-400kHz do work.
If the "sensor" has the I2C interface implemented in hardware and is not a processor and it does not use clock pulse stretching, then the SCL signal can be a strong high and low output, it does not need to be a open-drain output.
Which Arduino board do you use ?

1 Like

Thank you very much! This cleared up some things!

1 Like

I'm currently working with an Arduino Uno with an ATmega328P, but I already received an Uno R4 Minima that I plan to use for the 1MHz I2C (I am still having issues with getting the regular I2C to work on this board). I don't know about the I2C implementation on the sensor. About the overhead, I need to send some other configuration bytes every time as well, and this increases the overhead a lot. These would not be neccesary with the continuous writing I'm looking for.

What issue are you facing? I have Arduino UNO R4/WiFi. I may play around with your issue.