i2c functions using repeated start

Since, from what I've read, the Wire() library doesn't support repeated start in i2c (even though it says it does support it, I've heard many people saying it doesn't work), I wrote some simple functions that use digitalWrite and digitalRead on a couple pins to simulate i2c communication using a repeated start.

Here's the code:

#define RG03_sda 3
#define RG02_scl 2

uint8_t buf[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};


/*  i2cStart: 
 *    Starts i2c communication with the given device at the given register address. Uses a repeated start.
 *    i2cReadReg or i2cWriteReg should be called after this function.
 *
 *  Arguments:
 *    device     - the 7-bit i2c slave address for the device the user wants to access
 *    regAddress - the register that the user wants to access for either reading or writing
 *    
 *  Return:
 *    void
 */
void i2cStart(uint8_t device, uint8_t regAddress) {
  device = device << 1;
  
  //Start condition
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG03_sda, HIGH);
  digitalWrite(RG03_sda, LOW);
  digitalWrite(RG02_scl, LOW);
  
  //Pick slave device to write to
  int i = 0;
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((device >> (7-i)) & 0x01));
    digitalWrite(RG02_scl, HIGH);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG02_scl, LOW);
  
  //Choose register to access
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((regAddress >> (7-i)) & 0x01));
    digitalWrite(RG02_scl, HIGH);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG02_scl, LOW);
  
}


/*  i2cReadReg: 
 *    Accesses the given device and reads any number of bytes, starting at the register given previously from 
 *    the i2cStart function. Uses a repeated start at start. A stop signal is sent at the end of this function.
 *    The buffer is reset on every read, and the max number of bytes for reading is 10, minimum 1.
 *  
 *  Arguments:
 *    device   - The 7-bit i2c slave address for the device the user wants to access; should be the same as the address
 *               given in the i2cStart function.
 *    numBytes - The number of bytes that the user wants to read, starting at the register given previously from the
 *               i2cStart function.
 *
 *  Return:
 *    void
 */
void i2cRead(uint8_t device, uint8_t numBytes) {
  int i = 0;
  
  //reset buffer
  for(i = 0; i < 10; i++) {
    buf[i] = 0;
  }
  
  //Error checking
  if((numBytes < 1) || (numBytes > 10))
    return;
    
  uint8_t originalNumBytes = numBytes;
  
  //Set device in read mode
  device = device << 1;
  device |= 0x01;
  
  //repeat start
  digitalWrite(RG03_sda, HIGH);
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG03_sda, LOW);
  digitalWrite(RG02_scl, LOW);
  
  //choose correct device
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((device >> (7-i)) & 0x01));
    digitalWrite(RG02_scl, HIGH);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG02_scl, LOW);
  
  //switch pinmode so I can read from sda pin rather than send
  pinMode(RG03_sda, INPUT);
  
  //Read the data
  i = 0;
  while(i < 8) {
    buf[(originalNumBytes - numBytes)] |= (digitalRead(RG03_sda) << (7-i));
    digitalWrite(RG02_scl, HIGH);
    digitalWrite(RG02_scl, LOW);
    i++;
    
    if((i == 8) && (numBytes > 1)) {
      numBytes--;
      i = 0;
      
      //switch pinmode back to output on sda pin for acknowledge
      pinMode(RG03_sda, OUTPUT);
      
      //extra clock pulse to get ack bit
      digitalWrite(RG02_scl, HIGH);
      digitalWrite(RG02_scl, LOW);
      
      //switch pinmode back to input on sda pin
      pinMode(RG03_sda, INPUT);
    }
  }
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG02_scl, LOW);
  
  //switch pinmode back to output on sda pin
  pinMode(RG03_sda, OUTPUT);
  
  //stop
  digitalWrite(RG03_sda, LOW);
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG03_sda, HIGH);
  
  return;
}

/*  i2cWrite: 
 *    Accesses the device given in the i2cStart function and sends it new data to the register given in the
 *    i2cStart function. A stop signal is sent at the end of this function. Multiple-byte write is not supported.
 *
 *  Arguments:
 *    data - The byte of data the user wants to write into the register given in the i2cStart function.
 *
 *  Return:
 *    void
 */
void i2cWrite(uint8_t data) {
  //send the data
  int i = 0;
  for(i = 0; i < 8; i++) {
    digitalWrite(RG03_sda, ((data >> (7-i)) & 0x01));
    digitalWrite(RG02_scl, HIGH);
    digitalWrite(RG02_scl, LOW);
  }
  
  //pull data line low
  digitalWrite(RG03_sda, LOW);
  
  //extra clock pulse to get ack bit
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG02_scl, LOW);
  
  //stop
  digitalWrite(RG03_sda, LOW);
  digitalWrite(RG02_scl, HIGH);
  digitalWrite(RG03_sda, HIGH);
}

Please note that this code assumes that the device you are using uses Read = 1, and Write = 0.
You will also need to define RG03_sda and RG02_scl to whatever pins you want to use. In this case they are set to pins 3 and 2, respectively, but they can be used on whatever digital pins you like.
If the names RG03_sda and RG02_scl sound weird it's because I'm using a non-arduino board and I'm trying to follow a convention. Despite this, the code should still work with arduino.

I've tested with several sensors and the code has worked flawlessly, so it should work with your application as well if necessary. Also, if you don't want to use a global buffer array, then it should be pretty easy to add a pointer to the functions' arguments.

Hope this helps anyone needing a repeated start!

The repeated start should work with the Wire library. Are there problems with the recent library ?
For I2C it is not allowed to set SDA or SCL high, it is an open-collector bus. Release the line for '1' and make it low for '0'.

BUT THE ABOVE CODE NOT WORKING PLEASE HELP ME I NEED WRITE DATA CONTINUOUSLY MY CODE IS HERE...
#include <Wire.h>
#include <SoftwareSerial.h>
#define disk1 0xc0>>1 //Address of 24aa32a eeprom chip
//#define sda A4
//#define scl A5
char incomingByte;
unsigned int integerValue=4000;
void writeEEPROM(int deviceaddress, unsigned int eeaddress,unsigned int data )
{

Wire.beginTransmission(deviceaddress);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.write((int)(data >> 8)); // MSB
Wire.write((int)(data & 0xFF));
Wire.endTransmission();
}

void setup(void)
{
unsigned int address = 0;
Serial.begin(9600);
Wire.begin();

}

void loop(){

unsigned int address = 0;
if (Serial.available()) {
integerValue = 0;
while(1) {
incomingByte = Serial.read(); // char
if (incomingByte == '\ ') break;
if (incomingByte == -1) continue;
integerValue *= 10; // shift left 1 decimal place
// convert ASCII to integer, add, and shift left 1 decimal place
integerValue = ((incomingByte - 48) + integerValue);
}
}
Serial.println(integerValue); // Do something with the value
delay(500);
writeEEPROM(disk1, address, integerValue);

}

OUTPUT: DIGITAL VAL=4000, MY VOLT =4.8V THE I2C IS WORKING FOLLOWING MANOR: S C0 A 00 A 00 A F0 A 11 A P

THIS CODE IS USED TO DAC. THIS WORKING PREFECT IN ONE TIME. MY AIM THE DIGITAL VALUE IS PROVIDE BY USING SERIAL SO I NEED WRITE DATA CONTINUES PLEASE ANY ONE HELP ME.
I WILL TRY ANOTHER METHOD BY REPLACING THE LINE IN ABOVE CODE, THIS SEND DATA CONTINUES BUT IN THE DAC OUTPUT VOLTAGE NOT GET THIS MY PROBLEM.
THE CHANGES IS :Wire.endTransmission(false);
the output is:S SR C0 A 00 A 00 A F0 A 11 A SR C0 A 00 A 00 A F0 A 11 A ....... TO FOLLOW THIS MANOR. PLEASE HELP ME AM WAIT U R REPLY THANK YOU

One problem I see with your code:

void writeEEPROM(int deviceaddress, unsigned int eeaddress,unsigned int  data )
{
         
 Wire.beginTransmission(deviceaddress);
 Wire.write(highByte(eeaddress));
 Wire.write(lowByte(eeaddress);
 Wire.write(highByte(data));
 Wire.write(lowbyte(data));
 Wire.endTransmission();
      }

Another Problem, you are not checking to see if the EEProm has completed the prior Write cycle. It can take up to five milliseconds for the chip to erase a page, program the page.

Another possible issue: if the eeaddress is on a page boundary the highByte(data) will be stored correctly, but the lowByte(data) will rap to the beginning of the page. The 24AA32A (MicroChip) has a 32 byte write buffer. if ((eeaddress & 0x1f)==0x1f ) then lowByte(data) will be stored at (eeaddress &0xFFE0).

Example:

writeEEPROM(0x50,0x001f,0x1234);

Would store 0x12 in address 0x001F and 0x34 at address 0x0000.

writeEEPROM(0x50,0x003f,0xABCD);

Would store 0xAB at address 0x003F and 0xCD at address 0x0020

Here is a Library I wrote that addresses these issues.I2C_EEPROM

Chuck.

check out my KickStarter Memory Panes a 1MB external RAM Shield for Mega2560 projects.

hi in the coce am using address to shift both msb and lsb for example
Wire.beginTransmission(deviceaddress);
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.write((int)(data >> 8)); // MSB
Wire.write((int)(data & 0xFF));
Wire.endTransmission();
}
its working perfectly my ques is how to send continues data
please help and refer my above output

When you say 'send continues data' what do you mean?

Are you meaning 'How do I send continuously?' or 'How do I send more than one byte at a time?'
or 'How do I send blocks of data?'

With the I2C buss all communications are controlled by the 'master' usually the Arduino in this case.

typically the devices communication protocol is something like this:

Master sends Command Block specifying Device I2C buss address, Write Mode, then a sequence of bytes. Next the Master issues a STOP (Wire.endTransmission():wink: or a Repeat-Start (Wire.endTransmission(false);). This choice is dependent on the SPECIFIC Device (consult the device's DataSheet).

For the Master to 'READ' from the device it Sends a Command Block with the I2C Buss address of the device + READ mode, then it continues issuing SCL pulses while monitoring SDA to 'READ' the returning data from the SLAVE Device.

To WRITE data into a 24AA32A EEPROM device you need to do the following steps.
I2Caddress is device address usually between 0x50 and 0x57
DATA is buffer of data to be written to EEPROM
DataLen is length in bytes of data in DATA
dataAddress is 16bit address of were to store DATA in the EEPROM.

1: Is the Device 'READY'. This means, does the device respond to communication requests? or is it busy programming a memory location from a prior WRITE command?

2: loop back to step 1 until device responds as READY, or until a timeout is exceeded.

3: If timeout, return failure, abort WRITE.
Else Write data address to EEPROM:

uint8_t error=0; //Wiring failure code
uint8_t n=0; // offset into DATA for next byte to send

while((error==0)&&(DataLen>n)){
  error=ready(I2Caddress,10);
  if(error==0){
    Wire.beginTransmission(I2Caddress);
    Wire.write(highByte(dataAddress));
    Wire.Write(lowByte(dataAddress));
  1. Write data block to EEPROM minding PAGE buffer size (32bytes), and Wiring data buffer size(32byte). The Wiring 32 byte buffer already had 3 bytes used so far in this transaction (I2Caddress, highByte(dataAddress), lowByte(dataAddress), therefor there is only 29 bytes left in the buffer that can be used before a Wire.endTransmission() must be called.
    uint8_t tblocklen=29; // maximum datablock size that can be set to device  
    do{
      Wire.write(DATA[n]);
      tblocklen--; // countdown maximum possible byte that could be written during current I2C transmission.
      n++;
      If((tblocklen==0)||((dataAddress & 0x1F)==0x1F)||(n==DataLen)){ // I2C buffer is full, or at PAGE boundry, or all bytes transmitted.
        // the 0x1F is the highest possible address in the Device Page buffer (32-1)
        error = Wire.endTransmission(); //actually send block to device, wait for completion
        tblocklen=0; //
        }
      dataAddress++;  // increment for next byte.
      }while((error==0)&&(tblocklen>0));
   }


uint8_t ready(uint8_t I2Caddress, uint8_t timeout){// time out is in milliseconds
uint8_t error=0xff; // initialize to timeout error
unsigned long time=millis()+timeout;
do{
   Wire.begintransmission(I2Caddress);
   error = Wire.endTransmission();
   }while((error!=0)&&(time>millis());
return error;
}

5: check error to see if write succeeded.

I'll leave the READ code to you.

Chuck.

Check out my Kickstarter Project: Memory Panes a 1MB RAM expansion for the Mega2560

sir thanks for u r guidance . i need send data regular interval that means the data are given by serial terminal.
for example i send 200 by serial terminal the same data transmit again and again to the slave device. if suppose i replace a data in serial terminal that replaced data transmit again and again.this all in done in run time .this is my aim...

I am not understanding you.

If you need to repeatedly send the same data to the I2C device, why don't you just send it multiple times?

What is the part number for the I2C device you are using? Is it a DAC, or EEPROM?

I do not understand why you would want to send the same/repeating data to an EEPROM?

If you need to send a series of values to a DAC, if you need to regularly send data to a DAC you will need to have the data in a buffer and use something like this Timer Interrupt example. In the Interrupt callback you would place code to send then next data through the I2C buss (Wiring). As long as the I2C transaction completes before the next interrupt is generated and you have more data to send, it should work.

If all you are doing is reading data from Serial and sending it to I2C, what is the problem?


Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.

first sorry for my lack of communication.
am using dac mtc4725 to communicate arduino by using i2c .
my need is the digital value send through serial terminal when ever i want. here i done something that was i send data via serial the equivalent output voltage is provide.here i send data only one time after that i cant send that's my problem.

arulchozhan:
am using dac mtc4725 to communicate arduino by using i2c .

I hope this part number was a typo, I found a MCP4725 DAC from MicroChips.

I Don't have any experience with this specific part, but reading the Datasheet MCP4725 DAC It looks like the DAC expects fixed size data blocks.

I have attached a test sketch, please use it and tell me what the Serial Monitor reports.


Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.

MCP4725.ino (4.33 KB)

sorry to take more time to reply in u r code nothing is displayed in hardware .but in simulation like this "the address is miss match "

arulchozhan:
sorry to take more time to reply in u r code nothing is displayed in hardware .but in simulation like this "the address is miss match "

is this the error message?

DAC did not Respond.
Failure=received NACK on transmit of address.

If so, then the device never responded to the Arduino.

Have you verified you have it wired correctly?
My code

#define DACaddr 0x60

Defined the I2C address of the DAC as 0x60, The SpecSheet stated the address was dependent on specific partnumbers. Verify that 0x60 is correct. if not, change it.
With this error, there is no possible software solution. Either the Hardware is wired incorrectly, or the DACaddr value is incorrect.

Here is a function to list the accessible I2C devices on the bus.

void scan(){
Serial.println(" Scanning I2C Addresses");
Serial.print("   "); //skip all call address, most devices ignore it anyway.
uint8_t cnt=0;
for(uint8_t i=1;i<128;i++){
  Wire.beginTransmission(i);
  uint8_t ec=Wire.endTransmission(true);
  if(ec==0){
    if(i<16)Serial.print('0');
    Serial.print(i,HEX);
    cnt++;
  }
  else Serial.print("..");
  Serial.print(' ');
  if ((i&0x0f)==0x0f)Serial.println();
  }
Serial.print("Scan Completed, ");
Serial.print(cnt);
Serial.println(" I2C Devices found.");
}

Run and post the results.

Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.

hi sir finally i got output. thanks to guidence in my first code is correct ather then the address byte
in this code the slave address for mcp4725=0xc0;
in ltc2631-hz12 address is =0x10;
in ltc ic is onely byte transmission that meen the following format:
1.slave address (disk1 )
2.dac configure bit(the mention eadaddress )
3. data byte

hai sir, u have any idea about arduino micro and leonardo this two board i din't get serial output in serial moniter. micro print only the manully press the reset button in the serial moniter. please help me
if u know
thank u sir

arulchozhan:
hi sir finally i got output. thanks to guidence in my first code is correct ather then the address byte
in this code the slave address for mcp4725=0xc0;
in ltc2631-hz12 address is =0x10;
in ltc ic is onely byte transmission that meen the following format:
1.slave address (disk1 )
2.dac configure bit(the mention eadaddress )
3. data byte

I do not believe the MCP4725's address is 0xc0. I2C devices use 7bit address. The maximum value of a 7bit number is 0x7F.

Execute the following code, it will display a list of all device that respond on the I2C network.

Lets start from there.

post the results.

void scan(){
Serial.println(" Scanning I2C Addresses");
//Serial.print("   ");
uint8_t cnt=0;
for(uint8_t i=0;i<128;i++){
  Wire.beginTransmission(i);
  uint8_t ec=Wire.endTransmission(true);
  if(ec==0){
    if(i<16)Serial.print('0');
    Serial.print(i,HEX);
    cnt++;
  }
  else Serial.print("..");
  Serial.print(' ');
  if ((i&0x0f)==0x0f)Serial.println();
  }
Serial.print("Scan Completed, ");
Serial.print(cnt);
Serial.println(" I2C Devices found.");
}

Chuck.


Check out my Kickstarter Project Memory Panes an expansion RAM Shield for Mega2560's. It adds 1MB of RAM for those projects where 8KB is not enough.