EDIT - Corrected code is at the bottom of the OP.
Background
While working on a circuit to log carbon dioxide with a K30 CO2 sensor using an Adafruit Data Logging Shield, I ran into a problem. The I2C address of the RTC on the shield has a collision with the address of the CO2 sensor. The address of the RTC is baked into the silicon and cannot be changed, but the address of the K30 sensor is mutable. According to the I2C guide (see PDF in I2C/I2C_comm_guide, zipped PDF warning!) the I2C address of the sensor can be any value:
0x00 to 0x7F
Precedent
This appears to be a common problem. I have found multiple threads (see a, b, c, d, e, f) which have attempted to solve this challenge. Most either fizzle with positive results or suggest using a different interface. YET, according the the datasheet, it should be possible to hack this change to the EEPROM.
Workarounds I Am Avoiding
- K30 RX/TX - Since the K30 has both I2C and RX/TX pins, why not switch to using the RX/TX and skip this whole mess? The sensor is mounted on a breakout board with many other parts and a protective shield epoxied onto it. I would like to avoid digging into the hardware to reach these pins.
- GasLab software - the manufacturer provides the GasLab software to hack the sensors using a GUI over USB. Unfortunately, it only works for Windows.
- Choose a different RTC - I need something quick and easy to log values as I am time constrained. I don't want to reinvent the wheel when the Adafruit shield works so well.
- Address
0x7F
- According to the datasheet, I should be able to call all K30 sensors using this address, however I could not get it to work in practice when in the same circuit as the RTC.
My Hardware
I am running an Arduino Uno clone wiring the I2C to A4 and A5. The I2C lines have 2.2K pullups. The K30 is running at ~9V.
Sanity Check
Running the Arduino code provided by the manufacturer, I am able to read the sensor response, and it makes sense.
Code to change EEPROM
#include <Wire.h>
byte CheckSum(byte * buf, byte count) {
byte sum=0;
while (count>0) {
sum += *buf;
buf++;
count--;
}
return sum;
}
void setup() {
Serial.begin(9600);
Wire.begin();
delay(1000);
byte changebuf[4] = {0x31, 0x00, 0x69, 0x00};
changebuf[3] = CheckSum(changebuf, 3);
Wire.beginTransmission(0x68<<1);
for (size_t i = 0; i<sizeof(changebuf); i++) {
Wire.write(changebuf[i]);
}
Wire.endTransmission();
}
void loop() {}
The message is 0xD0, 0x31, 0x00, 0x69, 0x9A
. Bytes are:
- The default address shifted left by one (I have also tried plain
0x68
) - Command to write 1 byte to EEPROM
- Address in EEPROM to write
- Payload to write
- Checksum
These values are based on the datasheet.
After running this, I power cycle the device so the EEPROM values propagate to the RAM per the datasheet.
Observations
After running the above code, the address is not changed. The device still responds to the original default address 0x68
and fails to respond to the new address 0x69
.
The Question Restated
Why is my attempt to command the EEPROM of the K30 to change the value associated with the device I2C address failing? Is there something wrong with the byte sequence I send?
SOLUTION - Per accepted solution, I missed the obvious extra LSB in the byte string. I am now able to change the address. Here is the complete corrected code to achieve this:
#include <Wire.h>
byte CheckSum(byte * buf, byte count) {
byte sum=0;
while (count>0) {
sum += *buf;
buf++;
count--;
}
return sum;
}
void setup() {
Serial.begin(9600);
Wire.begin();
delay(1000);
byte changebuf[5] = {0x31, 0x00, 0x00, 0x69, 0x00};
changebuf[4] = CheckSum(changebuf, 4);
Wire.beginTransmission(0x68);
for (size_t i = 0; i<sizeof(changebuf); i++) {
Wire.write(changebuf[i]);
}
Wire.endTransmission();
}
void loop() {}