Tag: I2C

Arduino or ESP & BH1750 light sensor

Arduino or ESP & BH1750 light sensor

One of the sensors I bought is a BH1750 (on the print it looks like BHI750, but it’s not an I but a 1) light sensor which measures the available light and has an I2C interface.

Again, getting this to work is not that hard. First you need to download the BHI1750 Library

which you add to Arduino IDE with Sketch, Include Library, Add .ZIP Library and select your file. The library comes with an example. In my case the file was called BH1750Test.pde. My computer did not have an association set up for .pde but if you change it to .ino it will work.

I have tested this code with an Arduino Nano and an ESP8266 and both worked. If you want to connect it to another Arduino or are going to use a seperate power source, please make sure you do not supply it with more than 4.5 volts as that is it’s maximum input power.

A copy of the example code:

/*

Example of BH1750 library usage.

This example initalises the BH1750 object using the default
high resolution mode and then makes a light level reading every second.

Connection:
 VCC-5v
 GND-GND
 SCL-SCL(analog pin 5)
 SDA-SDA(analog pin 4)
 ADD-NC or GND

*/

#include 
#include 


BH1750 lightMeter;


void setup(){
  Serial.begin(9600);
  lightMeter.begin();
  Serial.println("Running...");
}


void loop() {
  uint16_t lux = lightMeter.readLightLevel();
  Serial.print("Light: ");
  Serial.print(lux);
  Serial.println(" lx");
  delay(1000);
}

It’s amazingly short.

As you will have seen, the sensor has five pins while we need four for I2C. The fifth pin is called addr:

bh1750back

and this can be used to set the I2C address of the sensor. Floating or connected to GND, the I2C address will be 0x23 which is also the default in the library. If you connect it to VCC the address will be 0x5C and you will have to modify the library (unfortunately). I am working on suggesting an update to this library.

Advertisements
Arduino & TSL2561

Arduino & TSL2561

There is a nice sensor called the TSL2561 which gives an indication of the brightness in both the visible and the infrared spectrum. It works on an I2C bus and has a library for the Arduino.

tsl2561back

You can download the library here and you install it like you would any other ZIP library; Sketch, Include Library, Add .ZIP Library. Don’t forget to restart your IDE.

The ZIP files comes with a directory called .github which you should remove (otherwise the IDE won’t stop talking about it). Also in the examples directory, the example ends with a .pde extension. On my computer .pde was not associated with the Arduino IDE so I renamed it to .ino.

There is an example in the library folder on how to use the libary:

#include <Wire.h>
#include "TSL2561.h"

// Example for demonstrating the TSL2561 library - public domain!

// connect SCL to analog 5
// connect SDA to analog 4
// connect VDD to 3.3V DC
// connect GROUND to common ground
// ADDR can be connected to ground, or vdd or left floating to change the i2c address

// The address will be different depending on whether you let
// the ADDR pin float (addr 0x39), or tie it to ground or vcc. In those cases
// use TSL2561_ADDR_LOW (0x29) or TSL2561_ADDR_HIGH (0x49) respectively
TSL2561 tsl(TSL2561_ADDR_FLOAT); 

void setup(void) {
  Serial.begin(9600);
  
  if (tsl.begin()) {
    Serial.println("Found sensor");
  } else {
    Serial.println("No sensor?");
    while (1);
  }
    
  // You can change the gain on the fly, to adapt to brighter/dimmer light situations
  //tsl.setGain(TSL2561_GAIN_0X);         // set no gain (for bright situtations)
  tsl.setGain(TSL2561_GAIN_16X);      // set 16x gain (for dim situations)
  
  // Changing the integration time gives you a longer time over which to sense light
  // longer timelines are slower, but are good in very low light situtations!
  tsl.setTiming(TSL2561_INTEGRATIONTIME_13MS);  // shortest integration time (bright light)
  //tsl.setTiming(TSL2561_INTEGRATIONTIME_101MS);  // medium integration time (medium light)
  //tsl.setTiming(TSL2561_INTEGRATIONTIME_402MS);  // longest integration time (dim light)
  
  // Now we're ready to get readings!
}

void loop(void) {
  // Simple data read example. Just read the infrared, fullspecrtrum diode 
  // or 'visible' (difference between the two) channels.
  // This can take 13-402 milliseconds! Uncomment whichever of the following you want to read
  uint16_t x = tsl.getLuminosity(TSL2561_VISIBLE);     
  //uint16_t x = tsl.getLuminosity(TSL2561_FULLSPECTRUM);
  //uint16_t x = tsl.getLuminosity(TSL2561_INFRARED);
  
  Serial.println(x, DEC);

  // More advanced data read example. Read 32 bits with top 16 bits IR, bottom 16 bits full spectrum
  // That way you can do whatever math and comparisons you want!
  uint32_t lum = tsl.getFullLuminosity();
  uint16_t ir, full;
  ir = lum >> 16;
  full = lum & 0xFFFF;
  Serial.print("IR: "); Serial.print(ir);   Serial.print("\t\t");
  Serial.print("Full: "); Serial.print(full);   Serial.print("\t");
  Serial.print("Visible: "); Serial.print(full - ir);   Serial.print("\t");
  
  Serial.print("Lux: "); Serial.println(tsl.calculateLux(full, ir));
  
  delay(100); 
}

It is quite an extensive example and that is because the sensor has quite some features which the example shows nicely.

This library unfortunately does not work on the ESP, but I did find another library which should work on both which I will discuss later.

Arduino or ESP & SI7021

Arduino or ESP & SI7021

The SI7021 is a high quality sensor for humidity and temperature. Of course there is a library for it for the Arduino IDE. As you can see in the picture, it has an I2C interface. The back side of the sensor:

si7021back

The connection is the same as with other I2C devices, use A4 SDA and A5 SCL or on the ESP D2 for SDA and D1 for SCL.

Below a slightly updated version of the example which comes with the library to have it compile correctly on the ESP8266.

#include 
#include 

#if defined(ESP8266)
// For the ESP8266 we need these to be defined
#define SDA 4
#define SCL 5
#endif

SI7021 sensor;

void setup() {
  Serial.begin(9600);
  Serial.println("Ready!");
#if defined(ESP8266)
// For the ESP8266 we need to start the sensor with SDA and SCL pin numbers
  sensor.begin(SDA,SCL);
#else
// For Arduino we can just call begin.
  sensor.begin();
#endif
}


void loop() {
  // temperature is an integer in hundredths
  float temperature = sensor.getCelsiusHundredths();
  temperature = temperature / 100;
  Serial.println("------------");
  Serial.print("Temperature in Celsius: ");
  Serial.println(temperature);

  // humidity is an integer representing percent
  int humidity = sensor.getHumidityPercent();
  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.println("%");

  // this driver should work for SI7020 and SI7021, this returns 20 or 21
  int deviceid = sensor.getDeviceId();
  Serial.print("This is a SI70");
  Serial.println(deviceid);

  Serial.println();
  Serial.println("Heating");
  // enable internal heater for testing
  sensor.setHeater(true);
  delay(20000);
  sensor.setHeater(false);
  Serial.println("Done heating");
  
  // see if heater changed temperature
  temperature = sensor.getCelsiusHundredths();
  temperature = temperature / 100;
  Serial.print("Temperature in Celsius: ");
  Serial.println(temperature);

  Serial.println();
  Serial.println("Cooling");
  //cool down
  delay(20000);

  // get humidity and temperature at same time (also saves power)
  si7021_env data = sensor.getHumidityAndTemperature();
  Serial.println();
  Serial.print("Temperature in Celsius: ");
  Serial.println(data.celsiusHundredths/100);
  Serial.print("Humidity: ");
  Serial.print(data.humidityBasisPoints/100);
  Serial.println("%");
  
  Serial.println();
  Serial.println("Pausing");
  delay(5000);
}
Arduino and LM75A

Arduino and LM75A

The LM75A is a temperature sensor on an I2C bus. It has a very large range (-55°C to 125°C) with a moderate accuracy. In the range of -25°C to 100°C (the range you will probably be in), the accuracy is ±2°C.

In my experience the accuracy tends to be stable so what you could do is calibrate it against a sensor with higher accuracy and then take the deviation and subtract (or add) it to your measured temperature.

There already is a library for the sensor which you can download the library here. You have to install this library through Sketch, Include Library, Add .ZIP Library.

I have compiled the example that is included with the library against both the Arduino Nano and the ESP8266 and the same code worked for both. Remember to use the standard ports for I2C with the ESP8266 (D2, GPIO4 for SDA and D1, GPIO5 for SCL).

/*
 * \brief Show temperature in degrees and fahrenheit every second
 *
 * \author Quentin Comte-Gaz <quentin@comte-gaz.com>
 * \date 8 July 2016
 * \license MIT License (contact me if too restrictive)
 * \copyright Copyright (c) 2016 Quentin Comte-Gaz
 * \version 1.0
 */

#include <LM75A.h>

LM75A lm75a_sensor/*(false, //A0 LM75A pin state
                   false, //A1 LM75A pin state
                   false, //A2 LM75A pin state)*/; // Create I2C LM75A instance

void setup(void)
{
  Serial.begin(9600);
  Serial.println("Temperatures will be displayed every second:");
}

void loop()
{
  float temperature_in_degrees = lm75a_sensor.getTemperatureInDegrees();

  if (temperature_in_degrees == INVALID_LM75A_TEMPERATURE) {
    Serial.println("Error while getting temperature");
  } else {
    Serial.print("Temperature: ");
    Serial.print(temperature_in_degrees);
    Serial.print(" degrees (");
    Serial.print(LM75A::degreesToFahrenheit(temperature_in_degrees));
    Serial.println(" fahrenheit)");
  }

  delay(1000);
}

As you can see, the code is relatively straight forward, initial the library and read from the sensor.

ESP8266 & Real Time Clock

ESP8266 & Real Time Clock

I bought a DS3231 which is a real time clock (RTC) with a cell battery on it. It works through the I2C interface so it should not be too difficult to set up.

It turns out there already is a library for it for Arduino and that same library works on the ESP. Even the code is the same.

To hook up the D3231 RTC you need four wires, 3.3V, GND, SDA and SCL. 3.3V (marked as VCC on the board) and GND are easy. Standard I2C ports are 4 for SDA and 5 for SCL. On the ESP this will be D2 for SDA and D1 for SCL.

Setting up the library is straight forward. Download it here and extract it to your Arduino library directory …\Arduino\libraries or something alone those lines. You have to rename the directory called RTCLib-Master to RTCLib and restart Arduino IDE (make sure to close all instances).

Next load the ds3231 example that you can find in …\Arduino\libraries\RTCLib\examples\ds3231. Upload it to your ESP and watch the serial monitor. You will see something like:

2016/9/21 (Wednesday) 17:26:31
 since midnight 1/1/1970 = 1474478791s = 17065d
 now + 7d + 30s: 2016/9/29 5:56:37

If you look at the code you can see it’s rather straight forward. You include Wire.h, the I2C library and RTCLib.h for the RTC library. Initiate the instance RTC_DS3231 rtc and you are set. There is some code in the setup to give the RTC time to boot up. The most important call is rtc.now() which returns the current date time in a DateTime type variable. Also note that the function TimeSpan takes four arguments, days, hours, minutes and seconds. The comment in the code suggests that TimeSpan(7,12,30,6) calculates the time ahead of 7 days and 30 seconds. This should be 7 days, 12 hours, 30 minutes and 6 seconds.

// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include 
#include "RTClib.h"

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

void setup () {

#ifndef ESP8266
  while (!Serial); // for Leonardo/Micro/Zero
#endif

  Serial.begin(9600);

  delay(3000); // wait for console opening

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, lets set the time!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }
}

void loop () {
    DateTime now = rtc.now();
    
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
    
    Serial.print(" since midnight 1/1/1970 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");
    
    // calculate a date which is 7 days and 30 seconds into the future
    DateTime future (now + TimeSpan(7,12,30,6));
    
    Serial.print(" now + 7d + 30s: ");
    Serial.print(future.year(), DEC);
    Serial.print('/');
    Serial.print(future.month(), DEC);
    Serial.print('/');
    Serial.print(future.day(), DEC);
    Serial.print(' ');
    Serial.print(future.hour(), DEC);
    Serial.print(':');
    Serial.print(future.minute(), DEC);
    Serial.print(':');
    Serial.print(future.second(), DEC);
    Serial.println();
    
    Serial.println();
    delay(3000);
}
Multiple Arduino I2C Slaves

Multiple Arduino I2C Slaves

Idea

After getting I2C working on Arduino as a Master and  as a Slave and getting the ESP to act as an I2C Master and fixing the annoying problem of the I2C bus not initializing properly when booting an Arduino I2C Slave, I wanted something more. I wanted to have multiple Arduino I2C Slaves talk to an ESP I2C Master. I also wanted to have some sort of check that the communication is timely etc.

In more detail, I want to have three Arduino I2C Slaves, each of which have 6 ‘slots’ and a ‘ball’ is ‘rolling’ over those 6 slots, meaning, at any time, one of the slots and only one will be filled with a number. The ‘rolling’ of the ‘ball’ differs per Arduino as well as the number that symbolizes the ball.

The ESP will have to talk to all three Slaves and then somehow show the results.

Implementation

The way I implemented this is to have each Arduino be responsible for a row which has 6 slots and the ESP will query all three of the Slaves and display the result in the serial monitor as a 3 x 6 grid.

It might not be a totally pratical assignment for the Arduino’s but I working up to something where I need this step of one Arduino keeping track of multiple slots in a row of something and one ESP/Uno/Mega knowing all the rows.

The implementation is as follows: each of the Arduino Slaves will get a unique I2C id (9, 10 and 11). The speed of the ball as well as the value will be determined by the I2C id. This way I can create one code and just change one variable at the top before uploading it to an Arduino. Of course, further down the line it would be nice if the I2C id is stored somewhere on the Arduino and all code is the same for all Slaves but that is the next step.

The Master will in one query all three Arduino’s and print the results in a grid.

I2C Master

The ESP I2C Master is the easiest one, so let’s start with that.

#include <Wire.h>

int sdaPin = 4;
int sclPin = 5;

#define GRIDCOLUMNS 6
#define GRIDROWS 3

byte positions[GRIDROWS][GRIDCOLUMNS];

void setup() {
  Wire.begin(sdaPin,sclPin);
  Wire.setClockStretchLimit(15000);
  Serial.begin(9600);
  Serial.println("I2C Master on ESP ready!");
  for (byte i=0;i<GRIDROWS;i++) {
    for (byte j=0;j<GRIDCOLUMNS;j++) {
      positions[i][j] = 0;
    }
  }
}

void showGrid() {
  Serial.println("Grid:");
  for (byte i=0;i<GRIDROWS;i++) {
    for (byte j=0;j<GRIDCOLUMNS;j++) {
      Serial.print(positions[i][j]);
      Serial.print(" ");
    }
    Serial.println();
  }
  Serial.println();
}

void getRow (byte i) {
  Wire.beginTransmission(9+i);
  Wire.write(0);
  Wire.endTransmission();
  Wire.requestFrom(9+i,GRIDCOLUMNS);
  byte j = 0;
  while (Wire.available()) {
    byte b = Wire.read();
    positions[i][j] = b;
    j++;
  }
}

void loop() {
  delay(2000);
  for (byte i=0;i<GRIDROWS;i++) {
    getRow(i);
  }
  showGrid();
}

I will only focus on the code that is added on top of the ESP I2C Master code that I described earlier.

The grid will be GRIDCOLUMNS wide and will have GRIDROWS number of rows. The value of each of the positions in the grid is kept in the two dimensional array positions. In setup() all of the positions are initialized with value zero.

The loop() function has a delay of 2 seconds and the reads GRIDROWS number of rows with the functionon getRow() after which the function showGrid() is called before starting over.

The showGrid() function shows all values of the positions, needly ordered in rows. Very straight forward.

The getRow() function connects to a I2C slave with id 9+i (i ranges from 0 to GRIDROWS-1 which in our case is 3-1). This means it will talk to slave 9, 10 and 11. It will first send the command 0 to it and than request it to send GRIDCOLUMNS number of bytes using requestFrom(9+i,GRIDCOLUMNS). The answers received are used to update the positions array.

I2C Slave

The I2C slave code for this idea is based on the Arduino Nano I2C Slave that I talked about earlier and updated after I found out about an I2C bus problem. The code:

#include <Wire.h>

#define I2CID 11
#define GRIDCOLUMNS 6

byte rowValue[2][GRIDCOLUMNS];
byte loopVar;
byte currentRow;

#define SERIAL false

void setup() {
  Wire.begin(I2CID);
  Wire.onRequest(requestEvent); // data request to slave
  Wire.onReceive(receiveEvent); // data slave received

#if SERIAL
  Serial.begin(9600);
  Serial.print("I2C Slave ready, listening on ");
  Serial.println(I2CID);
#endif

  currentRow = 0;
  loopVar = 0;
  for (byte j=0;j<=1;j++) {
    for (byte i=0;i<GRIDCOLUMNS;i++) {
      rowValue[j][i] = 0;
    }
  }
  rowValue[0][I2CID-9] = I2CID-8;
  rowValue[1][I2CID-9] = I2CID-8;
}

void receiveEvent(int countToRead) {
  while (0 < Wire.available()) {
    byte requestPos = Wire.read();
  }
}

void requestEvent() {
#if SERIAL
  Serial.print(".");
#endif
  Wire.write(rowValue[currentRow],GRIDCOLUMNS);
}

void loop() {
  delay(50);
  loopVar++;
  if (loopVar>I2CID) {
    loopVar = 0;
    for (int i=1;i<GRIDCOLUMNS;i++) {
      rowValue[1-currentRow][i] = rowValue[currentRow][i-1];
    }
    rowValue[1-currentRow][0] = rowValue[currentRow][GRIDCOLUMNS-1];
    currentRow = 1-currentRow;
  }
}

Again, I will only comment on the parts that are added on top of the original Slave code.

The array rowValue is the array that keeps track of what value is in each position. It is a two dimensional array because one of the rules was that one and only one position is filled. If I start moving the ‘ball’ around and it receives an I2C request during the moving, there can be two balls or no balls. I don’t want that so the new values are put in the other set of GRIDCOLUMNS and the variable currentRow knows which row is the one to send when requested to do so by I2C. The variable I2CID is, as the name suggest the id to be used on the bus. The variable loopVar is used to determine when to move the ball. This I will explain in the loop() function.

The setup() function set all values of the array rowValue to zero and one position, the same in both versions of the row, is filled with our id minus 8 which means the ball will be a 1, 2 or 3. Also, depending on the value of I2CID, another initial position is filled. Variables currentRow and loopVar are also initialized.

The loop() function starts by incrementing loopVar and once loopVar is greater than the I2CID, the ‘ball’ is moved. By using the I2CID I get that the ball moves at a different pace per row. The ball is moved by populating the not active row of rowValue with the moved ball and afterwards changing the value of currentRow to signal the new row is ready to be used.

The requestEvent() function is straight forward, just send the currentRow version of rowValue.

Wiring

The three Arduinos all have GND, VIn, A4 and A5 connected and GND of the ESP is connected to the common GND and GPIO4 (pin D2) and GPIO5 (pin D1) are connected to A4 and A5 respectively as shown in the picture of this article.

The ESP gets it’s power throught the USB port because we want to monitor it’s serial output and the Arduinos get their power through a seperate 5V source.

If all is connected and uploaded properly you should see results similar to this on your ESP serial monitor:

Grid:
0 0 0 0 0 1 
0 0 0 0 2 0 
0 3 0 0 0 0
Fix for I2C Arduino Slave communication

Fix for I2C Arduino Slave communication

I was very happy with my Arduino I2C Master and Arduino I2C Slave and of course the link to ESP with the ESP I2C Master. All of these worked well when connected to the computer and after uploading the code but I found out that if I reboot the Arduino I2C Slave by unplugging it from power and plugging it back in, the I2C bus would not initialize properly. Funnily (or better yet annoyingly) enough, if I opened a serial monitor on the Slave, communication would start again. I will save you the story of what I tried and go straight to the solution.

Let’s describe the problem first, two Arduino’s connected to each other with A4 connected to A4, A5 connected to A5 and GND connected to GND. Both connected via USB to a computer. One runs the Arduino I2C Master and the other runs the Arduino I2C Slave.

nanoi2cnano

When you have them connected and load the two programs on them via the Arduino IDE and watch on the serial monitor, all is fine. If you remove the Slave from the USB port and plug it back in again, the Master will not see the Slave. Oddly enough, the I2C bus communication will be restored (if you remove the only Slave, the Master seems to stop trying to write to the bus). Restored communication is however only partial, it can send on the I2C bus but it is not receiving. If you upload the program again on the Slave, communication is restored. You can also (re)open the serial monitor and the Master will again talk to the Slave. But of course, once you put it in ‘the field’ you do not have that luxury.

The solution that worked for me consists of two parts.

Step 1, remove any Serial communication from your program. I know, you need it for debugging etc. So what I have done is the following:

...
#define SERIAL true
....
#if SERIAL
  Serial.begin(9600);
  Serial.print("I2C Slave ready, listening on ");
#endif
....
etc...

So during testing, you will have defined SERIAL to be true and once your code works, you change true to false and reupload your code. Remember to put all of your serial communication in a #if SERIAL …. #endif section. Also, for some reason, your variable SERIAL cannot be the first one set if you want to use #if. Don’t know why but I am very happy with this post on the subject. Just put another variable before the #define SERIAL and you should be fine.

Step 2, do not power your Arduino through the USB connector but throught the VIn port. What I have done is cut a USB cable and soldered two breadboard wires to it:

usb-power

That’s it. I2C should now automatically work when power is cycled.

ESP, I2C Master

ESP, I2C Master

After getting the I2C Master running on the Arduino Nano, I wanted to have the same code running on the ESP8266. I have had I2C running on the ESP8266 before but that was in LUA. I wanted it to work with the Arduino IDE. Of course the code needed to be adapted a bit but I was happy to see it was not that much.

Let’s start out with the code:

#include 

int sdaPin = 4;
int sclPin = 5;

void setup() {
  Wire.begin(sdaPin,sclPin);
  Wire.setClockStretchLimit(15000);
  Serial.begin(9600);
  Serial.println("I2C Master on NodeMCU ready!");
}

void loop() {
  delay(50);
  Serial.println("Write data");
  Wire.beginTransmission(9);
  Wire.write(0);
  Wire.endTransmission();
  Serial.println("Receive data");
  Wire.requestFrom(9,3);
  String response = "";
  while (Wire.available()) {
      char b = Wire.read();
      response += b;
  }
  Serial.println(response);
}

As you can see, the code is nearly identical. The loop() function is identical. The only difference is in the first two lines of setup(). For the Arduino, you call the Wire.begin function without any parameters to start. For the ESP you have to tell it which ports you want for SDA and SCL. If you don’t it will default to 4 for SDA and 5 for SCL.

Please remember that for the ESP/NodeMCU we are talking about GPIO4 and GPIO5 and not pin 4 and 5.

The line Wire.setClockStretchLimit(15000) is the one that took forever to figure out. For some reason, without that line, the ESP does not wait long enough for the answer. So it will ask a question on the I2C bus but will just stop listening too soon. I finally found the answer on a forum.

Once we have uploaded the code, we can connect the ESP to our Arduino I2C Slave. Only three wires are needed. Connect GND to GND, connect SDA to SDA and SCL to SCL.

Nano, I2C Master and Slave

Nano, I2C Master and Slave

After creating the Nano I2C Master and the Nano I2C Slave we want to combine these into one setup where the master talkes to the slave.

First, make sure you have two Nano’s setup, one as master and one as slave. To join them so they can talk to each other, you have to make sure that they have a common ground, so connect GND from one to the GND of the other. Next is connecting the I2C bus. On the Nano this is connected to A4 (SDA) an A5 (SCL). Since I2C is a bus, you connect all SDA and all SCL together and not like serial where you cross TX and RX.

Please note, that if you do not have a common power feed that you should NOT connect the 3.3v or 5v pins.

When you have each of the two Nano’s connected to a USB port, this is what you result will look like:

nanoi2cnano

Orange is the SCL, blue is SDA and white is GND.

When you monitor the two Nano’s COM ports you will see this on the I2C Master (it will continuously repeat):

Write data
Receive data
PAM

The I2C Slave output will look like this (also continuosly repeating):

Receive event
Request event

Have you read how to monitor multiple devices with Arduino IDE.

Arduino Nano, I2C Slave

Arduino Nano, I2C Slave

Ok, so we have built the I2C Master. Now all we need are slaves to talk to.

Again, as with the I2C Master, the code for the I2C Slave is not that difficult. I will share the code first and than talk about it:

#include <Wire.h>

#define ANSWERSIZE 3

String answer = "PAM";

void setup() {
  Wire.begin(9);
  Wire.onRequest(requestEvent); // data request to slave
  Wire.onReceive(receiveEvent); // data slave received
  Serial.begin(9600);
  Serial.println("I2C Slave ready!");
}

void receiveEvent(int countToRead) {
  while (0 < Wire.available()) {
    byte x = Wire.read();
  }
  Serial.println("Receive event");
}

void requestEvent() {
  byte response[ANSWERSIZE];
  for (byte i=0;i<ANSWERSIZE;i++) {
    response[i] = (byte)answer.charAt(i);
  }
  Wire.write(response,sizeof(response));
  Serial.println("Request event");
}

void loop() {
  delay(50);
}

As we did in the I2C Master, we start with including the library Wire with the command #include <Wire.h>. Also in the header I have defined the size and answer that we will send. Of course in your application the answer will probably not be static but this will suffice to show the workings of I2C.

In the setup() function we have Wire.begin(9). Notice the 9 between the brackets which defines the id of the device. When we initialized Wire in the I2C Master, we did not put anything between the brackets.

The next to lines define which functions are called when we receive data, Wire.onRequest(), and when we are requested to send data, Wire.onReceive().

The function receiveEvent is called when a Wire.write is called by the I2C Master. As you can see, you continue to read data while Wire.available() which means data is still there to be received.

The function requestEvent is called when the I2C Master calls Wire.requestFrom. The Wire.write() function expects a array of bytes and a size as it’s parameters so I converted the string into an array of bytes.

In this case the loop() function does not do anything, so I just put a delay function in there.

As with the I2C Master example, the Serial functions are merely there to show that it works if you monitor it on your serial monitor.

Let’s put two Nano’s together and see the result.