14. Networking and communications

Assignment

  • [x] individual assignment: design, build, and connect wired or wireless node(s) with network or bus addresses

  • [ ] group assignment: send a message between two projects

Summary

This week I decided to get my Temperature and Humidity sensor working with the I2C protocol. But as explained in week 11, some tricks need to be applied to get it working properly.

So digging into the I2C protocol and the Arduino Wire functions, I managed to have it working!!

Individual assignment

I’ve decided to retake the DHT22-2 click from week 11 and use it with the I2C protocol. The simple application of the Arduino WIRE library failed, so I have to adapt it to the requirements of this CM2322 sensor…

Test set-up

As explained in week 11, to use the I2C protocol on the DHT22 2 click with my mainboard, I need to:

  • connect SDA pin (data line) on pin 27 of the ATmega328P
  • connect SCL pin (clock line) on pin 28 of the ATmega328P
  • GND and 5V pins on mainboard connected to respective pins on DHT22 2 click.

I2C protocol

Let’s have a look into the main features of the I2C protocol:

  • All transactions begin with a START (S) and are terminated by a STOP (P).
    A HIGH to LOW transition on the SDA line while SCL is HIGH defines a START condition.
    A LOW to HIGH transition on the SDA line while SCL is HIGH defines a STOP condition.

  • Every byte put on the SDA line must be eight bits long. The number of bytes that can be transmitted per transfer is unrestricted. Each byte must be followed by an Acknowledge bit. Data is transferred with the Most Significant Bit (MSB) first
  • If a slave cannot receive or transmit another complete byte of data until it has performed some other function, for example servicing an internal interrupt, it can hold the clock line SCL LOW to force the master into a wait state. Data transfer then continues when the slave is ready for another byte of data and releases clock line SCL.
  • The acknowledge takes place after every byte. The acknowledge bit allows the receiver to signal the transmitter that the byte was successfully received and another byte may be sent.
    The master generates all clock pulses, including the acknowledge ninth clock pulse.
    The Acknowledge signal is defined as follows: the transmitter releases the SDA line during the acknowledge clock pulse so the receiver can pull the SDA line LOW and it remains stable LOW during the HIGH period of this clock pulse.
    When SDA remains HIGH during this ninth clock pulse, this is defined as the Not Acknowledge signal. The master can then generate either a STOP condition to abort the transfer, or a repeated START condition to start a new transfer.

  • Data transfers follow the format shown in Figure 9. After the START condition (S), a slave address is sent. This address is seven bits long followed by an eighth bit which is a data direction bit (R/W) — a ‘zero’ indicates a transmission (WRITE), a ‘one’ indicates a request for data (READ). A data transfer is always terminated by a STOP condition (P) generated by the master

CM2322 I2C protocol

The CM2322 sensor requires some adaptation to the standard I2C protocol. The communication with the sensor needs to fulfill following steps/process:

  • Wake up the sensor first.
    The microcontroller sends following frame:

START + sensor address (0xB8 in read mode, R/W bit set to 0) + wait ( > 800µsec) + STOP

This is done with following commands:
Wire.begin: which calls twi_init(), to initialize the TWI module: * initialize state * activate internal pullups for twi * initialize twi prescaler and bit rate * enable twi module, acks, and twi interrupt: (TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA))

Wire.beginTransmission, which prepares the frame to be sent: a byte including the slave address and a READ bit, TX buffer is kept empty.

Wire.endTransmission, which calls twi_writeTo(), with the sensor address (txAddress), an empty transmit buffer (txBuffer, txBufferLength), 1, sendStop);

In this function (twi_writeTo()) is the first trick: the effective address byte to send is constructed as a 7-bits address to which the R/W bit set to 0 is added! So, instead of using 0xB8 (=B1011 1000), we have to use the corresponding 7-bits address: B101 1100, 0x5C or 92 in decimal as sensor address, when using it explicitly (not scanning the I2C to receive it…).

Once this is done, the routine sends a START condition:

(TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE) | _BV(TWSTA);)

The I2C Interrupt is then enabled, the microcontroller will jump to the interruption routine ISR(TWI_vect) vector for the I2C

During normal I2C communication, the slave will send an ACK ( drive SLA line to LOW) when it receives its own address, which will allow further operations. In the case there is no ACK received when address has been sent (TW_STATUS = TW_MR_SLA_NACK, defined in the AVR Libc Reference Manual for the TWSR values as code 0x20), the routine twi_stop() is called, which send a STOP condition:

TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTO);

This is where we need to check if the minimum time of 800µs before sending the STOP condition is fulfilled, in order to be able to send a new frame requesting the data before the sensor goes to sleep again

Let’s try to send continuously a I2C frame with the sensor address only, without requesting data, with a delay of half a second between the requests, e.g. before the sensor goes to sleep…

/* I2C Protocol adapted for CM2322 sensor
CM2322 sensor I2C address is 0x5C , and with R/W bit set to 0: 0xB8
 */

#include <Wire.h>

byte x = 0x00;
void setup()
{
  Wire.begin();

  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}

void loop()
{
  byte error, address =0x5C;

  Wire.beginTransmission(address); // LSB = 0 : write
  error = Wire.endTransmission();

  if (error == 2)
  {
     Serial.print("Waking up the sensor , no ack received from address 0x");
     Serial.print(address,HEX);
     Serial.println("  !");
  }

  if (error == 0)
    {
      Serial.print("Sensor awake, second frame sent prior the sensor goes to sleep! I2C device found at address 0x");
      Serial.print(address,HEX);
      Serial.println("  !");

    }
  if (error==4)   
    {
      Serial.print("Unknow error at address 0x");
      Serial.println(address,HEX);
     }    

  delay(500);           // wait 500 milliseconds for next scan
}  

Yeah! It works:

We can now complete the code to get the complete data:

  • Request data to the sensor.
    The microcontroller sends following frame:

START + sensor address (0xB8 in read mode, R/W bit set to 0) + 0x03 (function code) + 0x00 (starting address)+ 0x04 (register length) + STOP

This is achieved with following commands:
Wire.beginTransmission
Wire.write
Wire.endTransmission

  • And finally read data sent by the sensor.

The sensor sends following frame:

0x03 (Function Code) + 0x04 (Data Length) + (Humidity High byte) + (Humidity Low byte) + (Temperature High byte) + (Temperature Low byte) + (CRC Check Code Low byte) + (CRC Check Code High byte)

This is achieved with following commands from the microcontroller:
Wire.requestFrom
Wire.available
Wire.read

The real values need finally to be computed from the High and Low bytes and displayed on the Serial monitor.

Here is the complete code:

/* I2C Protocol adapted for CM2322 sensor

CM2322 sensor I2C address is 0x5C ,
with R/W bit set to 0 (write): 0xB8 = 1011 1000
with R/W bit set to 1 (read): 0xB9 = 1011 1001
 */

#include <Wire.h>

byte data[8];

void setup()
{
  Wire.begin();

  Serial.begin(115200);
  Serial.println("\nI2C Scanner");
}

void loop()
{
  byte i, error;
  byte address =0x5C;// 0xB8 = 1011 1000
  byte READ_Data = 0x03;
  byte READ_Start = 0x00;
  byte READ_Length = 0x04;
  float h,t;

/* Wait a few seconds between measurements */
  delay(5000);

/* Wakes the sensor up*/
  Wire.beginTransmission(address); // LSB = 0 : write
  error = Wire.endTransmission();

/*Request data to sensor */
  Wire.beginTransmission(address); // LSB = 0 : write
  Wire.write(READ_Data);        // sends function code  to read sensor data
  Wire.write(READ_Start);       // sends starting register address
  Wire.write(READ_Length);      // sends register length  
  Wire.endTransmission();       // stop

/* Read data from sensor */
   i=0;
   Wire.requestFrom(0x5C, 8);    // request 8 bytes from sensor
   while (Wire.available()) { // slave may send less than requested
     data[i] = Wire.read(); // receive a byte as part of a table
    i++;
  }

/* compute humidity */
  h = ((word)data[2]) << 8 | data[3];
  h *= 0.1;

/* compute temperature */  
  t = ((word)(data[4] & 0x7F)) << 8 | data[5];
  t *= 0.1;
  if (data[4] & 0x80)
    t *= -1;

  Serial.print(F("Humidity: "));
  Serial.print(h);
  Serial.print(F("%  Temperature: "));
  Serial.print(t);
  Serial.println(F("°C "));

}

And here are the results on the serial monitor, showing the effect of blowing and warming up, as in week 11

Done!

Still to do if I had time....

Nothing more!