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!