Writing to a specific I2C register

I am trying to write to a specific register, but value doesn't change.

Working with SenseAir Sunrise CO2:

Info from i2c guideline from sensor manufacter

Measurement Period (EE) 0x96 (MSB) 0x97 (LSB)
Measurement period in seconds (range from 2 to 65534).
Odd numbers will be rounded up to nearest even number.
A system reset is required after changing configuration. Default value is 16.

And that's my code snippet:

int16_t mPeriod = 8;
Wire.beginTransmission(target); // target = 0x68
Wire.write((byte)0x96); //register addr
Wire.write(highByte(mPeriod)); // sends MSB
Wire.write(lowByte(mPeriod)); // sends LSB
error = Wire.endTransmission(true);
delay(EEPROM_UPDATE_DELAY_MS); // = 25ms

Error code is ok, but after reset, register value is the same.

What am i doing wrong? maybe someone can help with that?

Read pages 5 and 6 of the referenced data sheet about the need for a wake up byte.

I think that your snippet should begin with 2x

Wire.beginTransmission(target); // target = 0x68
Wire.beginTransmission(target); // target = 0x68

i will try that, but i have another function before, that wakeup sensor

  /* Wakeup */
  if(!(_wakeup(target))) {
    Serial.print("Failed to wake up sensor.");
    /* FATAL ERROR */
    while(true);
  }

  Serial.println("Changing Measurement Period...");

  uint16_t mPeriod = 10; 

  Wire.beginTransmission(target);
  Wire.write(0x96);
  Wire.write(highByte(mPeriod));      // sends MSB
  Wire.write(lowByte(mPeriod));       // sends LSB
  error = Wire.endTransmission(true);
  delay(EEPROM_UPDATE_DELAY_MS);

if(error != 0) {
    Serial.print("Failed to send request. Error code: ");
    Serial.println(error); 
    /* FATAL ERROR */
    while(true);
  }

  Serial.println("Sensor restart is required to apply changes");

and _wake(uint8_t target)

bool _wakeup(uint8_t target)
{
  int attemps = ATTEMPTS;
  int error;
 
  do {
    uint8_t byte_0;    
    /* */
    Wire.beginTransmission(target);
    error = Wire.endTransmission(true);
  } while(((error != 0 /*success */) && (error != 2 /*Received NACK on transmit of address*/) && (error != 1 /* BUG in STM32 library*/)) && (--attemps > 0)); 
  /* STM32 driver can stack under some conditions */
  if(error == 4) {
    /* Reinitialize I2C*/
    reInitI2C();
    return false;
  } 
  return (attemps > 0);
}

i copy this function from SenseAir S11 examples for ArduinoIDE

@ cattledog tried

Wire.beginTransmission(target);
  Wire.beginTransmission(target);
  Wire.write(0x96);
  Wire.write(highByte(mPeriod));      // sends MSB
  Wire.write(lowByte(mPeriod));       // sends LSB
  error = Wire.endTransmission(true);
  delay(EEPROM_UPDATE_DELAY_MS);

result the same :worried:
i use this code examples, but they doesn't have the code snippet with write random number to register with MSB and LSB

The Wire.beginTransmission() does not start something on the I2C bus.
The Wire.write() does not write something to the I2C bus.
The Wire.endTransmission() may not be used on its own to stop something.

I did not read the datasheet, sorry.

thanks, but these moments in the code I think work well because I use code examples obtained from the sensor manufacturer and they work well.
I need a little more advanced functionality, and in the code provided by them there are no examples how to reach it

There is an Arduino library for the device here
https://github.com/albrdev/Sunrise

From what I can see in the source code, the general approach you are using is correct.

1 Like

Which Arduino board do you use ?

The Senseair Sunrise CO2 sensor: https://senseair.com/products/power-counts/sunrise/

I read the document about the I2C interface and I have read the library source code, and I'm not happy with it.

The whole point of the wake-up is that it can not be detected if that was successful. Yet they are trying to do that with code that does not work on every Arduino board.
I can't deal with that wake-up code, and I don't know how it will influence when writing data to the sensor.

Could you try to read the temperature ?

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

  Wire.begin();   // it also sets the default of 100kHz
}

void loop()
{
  // The wake-up is a write command to the I2C address of the sensor.
  // The wake-up results in a error returned by Wire.endTransmission(),
  // because the sensor does not acknowledge that.
  // It is supposed to work that way.
  Wire.beginTransmission( 0x68);
  Wire.endTransmission();                   // ignore the return value

  // Set register address of the sensor
  Wire.beginTransmission( 0x68);
  Wire.write( 0x08);                        // register address
  int error = Wire.endTransmission( false); // false for a repeated start

  if( error != 0)
    Serial.println( "ERROR, can't find the sensor");

  // Read the temperature (two bytes, representing a signed integer)
  int n = Wire.requestFrom( 0x68, 2);       // request 2 bytes

  if( n != 2)
    Serial.println( "ERROR, can't read data");

  byte msb = (byte) Wire.read();            // msb is at 0x08, lsb is at 0x09
  byte lsb = (byte) Wire.read();
  int16_t temp = word( msb, lsb);
  float temperature = float( temp) / 100.0;
  Serial.println( temperature);

  delay( 1000);
}
1 Like

i use esp32, not arduino.

and previously i use that code:


#include <Wire.h>

void setup() {
  Wire.begin();                // join i2c bus (address optional for master)
  Serial.begin(9600);          // start serial communication at 9600bps
}

int reading = 0;
float temp=0;

void loop() {
  // step 1: wake sensor
  Wire.beginTransmission(byte(0x68)); // transmit to device 0x68=104
  Wire.endTransmission();      // stop transmitting

  delay(5);       //max 15ms            

  // step 3: instruct sensor to return a particular echo reading
  Wire.beginTransmission(byte(0x68)); // transmit to device #104
  Wire.write(byte(0x06));      // sets register pointer to echo #1 register (0x06)
  Wire.endTransmission();      // stop transmitting

  // step 4: request reading from sensor
  Wire.requestFrom(byte(0x68), 2);    // request 2 bytes from slave device #104

  // step 5: receive reading CO2 from sensor
  if (2 <= Wire.available()) { // if two bytes were received
    reading = Wire.read();    // receive high byte (overwrites previous reading)
    reading = reading << 8;    // shift high byte to be high 8 bits
    reading |= Wire.read();   // receive low byte as lower 8 bits
    Serial.print(reading);   // print the reading
    Serial.println(" CO2");
  }
  
 // step 6: read temperature
  Wire.beginTransmission(byte(0x68)); // transmit to device 
  Wire.write(byte(0x08));      // sets register pointer to echo #1 register (0x08)
  Wire.endTransmission();      // stop transmitting
  Wire.requestFrom(byte(0x68), 2);    // request 2 bytes from slave device 
   if (2 <= Wire.available()) { // if two bytes were received
    reading = Wire.read();    // receive high byte (overwrites previous reading)
    reading = reading << 8;    // shift high byte to be high 8 bits
    reading |= Wire.read();   // receive low byte as lower 8 bits
    temp=reading;
    Serial.print((temp/100));   // print the reading
    Serial.println(" C");
  }
  delay(5000);                  // wait a bit since people have to read the output :)
}

and it work good, but i need change value on some registers, so i search another examples of how to do that

Also i try your code, and it's working fine.

I have some progress, i write two separate codes for write and read from registers, that need to sensor restart.

@Koepel thanks for code examples, i use that and it seems to be work

Code for WRITE to register

  // The wake-up is a write command to the I2C address of the sensor.
  Wire.beginTransmission( 0x68);
  Wire.endTransmission();                   // ignore the return value

  uint16_t mPeriod = 8;
  Wire.beginTransmission(0x68);
  Wire.write(0x96);
  Wire.write(highByte(mPeriod));      // sends MSB
  Wire.write(lowByte(mPeriod));       // sends LSB
  error = Wire.endTransmission(true);
  delay(50);

  if( error != 0) Serial.println( "ERROR, can't find the sensor");

code for READ from register

  // The wake-up is a write command to the I2C address of the sensor.
  Wire.beginTransmission( 0x68);
  Wire.endTransmission();                   // ignore the return value

  // Set register address of the sensor
  Wire.beginTransmission( 0x68);
  Wire.write( 0x96);                        // register address
  error = Wire.endTransmission( false); // false for a repeated start

  if( error != 0) Serial.println( "ERROR, can't find the sensor");

  // Read the temperature (two bytes, representing a signed integer)
  int n = Wire.requestFrom( 0x68, 2);       // request 2 bytes

  if( n != 2)
    Serial.println( "ERROR, can't read data");

  byte msb = (byte) Wire.read();            // msb is at 0x08, lsb is at 0x09
  byte lsb = (byte) Wire.read();
  uint16_t value = word( msb, lsb);
  Serial.println( value );

Now we are getting somewhere :smiley:

Can you put those two in a function ?
The fastest way is to make a few functions for reading and writing 8-bit bytes and signed and unsigned 16-bit integers.
For example read_u8, read_s16, write_u8, write_u16.

Then make a sketch that:

write the measurement period
delay
write the reset command, single byte 0xFF to register 0xA3.
delay, 1 second or so.
check if the measurement period is still the same.

If that works, then you can try to turn off the power and check if the measurement period is still the new value.

The reset command should have everything that a normal write command has, with wake-up and so.

Please show a full sketch.

1 Like

My main goal was to use the opportunity to get the value compensated by atmospheric pressure.
But for this it was necessary to understand how to write in registers, now thanks to you and a few hours of tests, I finally understood this :slight_smile:

Now the code looks like this, everything works fine, I haven't rewritten everything with functions for reading / writing from registers, I'll do it later.

I would also like to make sure that I write and read the value of atmospheric pressure correctly, can you see?
Everything seems to be working well, but maybe I missed something.

    //Write pressure value to register BAROMETRIC_PRESSURE_VALUE
    int16_t co2Pre = 9772; //977.2 hPa

    // The wake-up is a write command to the I2C address of the sensor.
    Wire.beginTransmission(SUNRISE_ADDR);
    Wire.endTransmission();                   // ignore the return value
    
    Wire.beginTransmission(SUNRISE_ADDR);
    Wire.write(BAROMETRIC_PRESSURE_VALUE);
    Wire.write(highByte(co2Pre));      // sends MSB
    Wire.write(lowByte(co2Pre));       // sends LSB
    error = Wire.endTransmission(true);
    delay(50);
  
    if( error != 0) Serial.println( "ERROR, can't find the sensor");
    //read BAROMETRIC_PRESSURE_VALUE
    error = WireRequest(SUNRISE_ADDR, BAROMETRIC_PRESSURE_VALUE, 2);
    if(error != 0) {
      Serial.println( "Failed to write to target.");
      while(true);
    }
    msb = (byte) Wire.read();
    lsb = (byte) Wire.read();
    int16_t co2Pressure = word( msb, lsb);
    float co2PressureValue = float( co2Pressure) / 10;
    Serial.println("BAROMETRIC_PRESSURE_VALUE: " + (String) co2PressureValue);

Not everything of the sensor is clear to me.

That delay of 50ms is normally not needed.

Please don't use the WireRequest() from the library. It has that WakeUp function that might not work well on some boards. I would have to look into the source of the ESP32 Wire library and compare it with the source code of the Sunrise library, and even then I'm still not sure.

The Wire.requestFrom() from the Arduino library does not return an error, it returns the number of read bytes.

Only after setting the EN pin high, then 35 ms is needed and sometimes 2.4 seconds is needed.
I do not know how the sensor behaves if something is done before the waiting time.

later i use delay of 25 ms after register write according to datasheet, and i increased it to 50 ms, for i don't know why :slight_smile:

i use my own function WireRequest based on your example

int WireRequest(byte target, byte regAddr, int numBytes) {
  // The wake-up is a write command to the I2C address of the sensor.
  Wire.beginTransmission(target);
  Wire.endTransmission();                   // ignore the return value
  
  // Set register address of the sensor
  Wire.beginTransmission(target);
  Wire.write(regAddr);                        // register address
  int error = Wire.endTransmission(false); // false for a repeated start

  if( error != 0) {
    Serial.println( "ERROR, can't find the sensor"); 
    return 1;
  }

  // Read the temperature (two bytes, representing a signed integer)
  int n = Wire.requestFrom(target, numBytes);       // request 2 bytes

  if( n != 2) {
    Serial.println( "ERROR, can't read data"); 
    return 1;
  }

  return 0;
}

Allright, but check the number of bytes returned by Wire.requestFrom with "if( n != numBytes)" :

  int n = Wire.requestFrom(target, numBytes);       // request 2 bytes

  if( n != numBytes) {           // was the same amount of bytes received as was requested ?
    Serial.println( "ERROR, can't read data"); 
    return 1;
  }

It is better to keep everything together in one function. For example reading a variable number of bytes. If you split things, then you have to think every time you use the function.

ok, thanks, i edit that function.

i have one more question, before i can get pressure compensated CO2 value, i need to enable pressure compensation, later when i tried everything i somehow enable it, but i want to know how to correct do it.
Datasheet has info about that on 32 page

Is that code good for that?

    // The wake-up is a write command to the I2C address of the sensor.
    Wire.beginTransmission(SUNRISE_ADDR);
    Wire.endTransmission();                   // ignore the return value
    
    Wire.beginTransmission(SUNRISE_ADDR);
    Wire.write(0xA5); //Meter control
    Wire.write(0xE0);
    error = Wire.endTransmission(true);  
    if( error != 0) Serial.println( "ERROR, can't find the sensor");

    Serial.println("\n\nSensor restart to apply changes\n\n"); 
    
    delay(2500); //Wait after sensor restart

Or maybe i need that?

read Meter control register
edit bits using bitWrite() function
write edited bits to register

tried that code

...
Wire.beginTransmission(SUNRISE_ADDR);
Wire.write(0xA5); //Meter control
Wire.write(0xE0);  //enable or 0xF0 - disable
error = Wire.endTransmission(true);  
....

and it only disable pressure compensation(i think value that i send not correct, and chip set values by default), because i request values from two registers
CO2_FILTERED_PRESSURE_COMPENSATED: 429
CO2_FILTERED_WITHOUT_PRESSURE_COMPENSATING: 429
and it's the same, but must be different

I will try next variant from previous message

done, always working using second variant;

read Meter control register
change bit 4 using bitWrite(meterControl, 4, 0);
write changed bits to Meter control register

that's all.

@Koepel big thanks for help

Yes, if you want to change 1 bit, then your have to do the complete read sequence and the complete write sequence, and maybe another read sequence to verify :wink:

1 Like