I2C between Maker Nano and Kid-Bright32 (Esp32)

In our last post, we started looking at the workings of the I2C protocol. In case you missed that, you can read about that here. Today, I will continue with I2C by showing you how to implement the protocol between an Maker Nano (a Arduino Nano Clone) and Kid-Bright32 (a ESP32 based development and Education device).

This project will eventually be turned into an IoT device, with Google Assistant voice control. The Kid-Bright have very limited IO pins, and the Maker Nano has no Network Connectivity unless we use an Ethernet Shield. Enough of that for now, let us start with today’s tutorial, we will get back to this project later…

The Code for the Master

/* I2C Master Code - Kid Bright 32 v 1.3
   Can be adapted to Arduino or NodeMCU or STM32
   As it uses no special libraries, only the standard Wire.h
   that is already included with the Arduino IDE
*/

#include <Wire.h>

#define button1 16 // Button 1
#define button2 14 // Button 2
#define led_blue 17 // Led 1
#define led_red 2 // Led 2
#define led_yellow 15 // Led 3


void setup() {
  Wire.begin(); // Start I2C, join bus as a Master
  pinMode(button1,INPUT_PULLUP); // Set as input
  pinMode(button2,INPUT_PULLUP); 
  pinMode(led_blue,OUTPUT); // Set as output
  pinMode(led_red,OUTPUT);
  pinMode(led_yellow,OUTPUT);
  digitalWrite(led_blue,HIGH); // LED is Active Low
  digitalWrite(led_red,HIGH); // LED is Active Low
  digitalWrite(led_yellow,HIGH); // LED is Active Low
  Serial.begin(115200); // Start Serial for debugging
}

byte Data = 0; // Variable for sending data to the slave
int SlaveData = 0; // Variable for receiving data from the slave

void loop() {
  Wire.beginTransmission(4); // Send data to Slave at address #4
  Wire.write(Data); // Transmit one byte ( 8 bits)
  Wire.endTransmission(); // End Transmission
  Wire.requestFrom(4,1); // Request one (1) byte of data from the      //slave at address$
  while (Wire.available()) { // If data received
    SlaveData = Wire.read(); // Save it into a variable
  }

// We will implement a simple latch in software, where a single 
// button latches or releases a bit with every press and release.  
// This code should ideally include debouncing as well. It was left
// out for clarity.

  if (digitalRead(button1) == LOW) { // If button 1 pressed
    if (bitRead(Data,0) == HIGH) { // test if bit 0 in variable is set
      bitClear(Data,0); // clear it if it is set
    } else {
      bitSet(Data,0) == HIGH; // set bit to high
    }
  } 
// Do the same for the second button

  if (digitalRead(button2) == LOW) {
    if (bitRead(Data,1) == HIGH) {
      bitClear(Data,1);
    } else {
      bitSet(Data,1) == HIGH;
    }
  } 
 
// We will test for a set bit in the transmitted byte, and invert it, 
// as the LED's are active LOW

  digitalWrite(led_blue,!bitRead(Data,0)); // Toggle Led 1
  digitalWrite(led_red,!bitRead(Data,1)); // Toggle Led 2

// Same with the data received from the slave

  digitalWrite(led_yellow,!bitRead(SlaveData,0)); // Toggle Led 3
  
// Print Debug info on serial port
  
  Serial.print("Send to Slave 0xb");
  Serial.println(Data,BIN);
  Serial.print("Received from Slave 0xb");
  Serial.println(SlaveData,BIN);
  
 // Small delay, should change to millis in production code

  delay(200);
       
}
The Master Device. Connections are +5v (red) SCL (brown) SDA (orange) Ground (blue)

Code for the Slave

/*
     I2C Slave
     Arduino Nano or Compatible, can be used with ESP32 or STM32
     as well as no special libraries used, only standard Wire.h
*/

#include <Wire.h>

#define button 2 // A user button
#define led1 3 // Led 1
#define led2 4 // Led 2

byte Data = 0; // Variable for sending data to the Master

void setup() 
{
  pinMode(button,INPUT_PULLUP); // Set as Input
  pinMode(led1,OUTPUT);
  pinMode(led2,OUTPUT);
  digitalWrite(led1,LOW); // Led is Active High, so switch it off
  digitalWrite(led2,LOW);
  Wire.begin(4); // Join I2C Bus as device #4
  Wire.onReceive(receiveEvent); // Register receive Event
  Wire.onRequest(requestEvent); // Register request event
  Serial.begin(115200); // start serial debugging
  
}

void loop() {
// implement a software bit latch, on bit 0 of the Data variable
// the latch is toggled by pressing and releasing the button
// should ideally be debounced as well

 if (digitalRead(button) == LOW) {
  if (bitRead(Data,0) == HIGH) {
    bitClear(Data,0);
  } else {
    bitSet(Data,0) == HIGH;
  }
 }
 delay(200); // small delay
}

// This event will be triggered when the master requests data

void requestEvent() 
{
  Wire.write(Data); // Send data to the master
  Serial.print("Sending to Master 0xb");
  Serial.println(Data,BIN);
}
// This event gets triggered when the master sends data

void receiveEvent(int Quantity)
{
  int x = Wire.read(); // read the data ( one byte in this case)
  digitalWrite(led1,bitRead(x,0)); // Toggle LED 1 on Bit 0 state
  digitalWrite(led2,bitRead(x,1)); // Toggle LED 2 on Bit 1 state
  Serial.print("Received from Master 0xb"); // Debugginh
  Serial.println(x,BIN);

}
Maker Nano on an IO Shield, Connections are +5v (red) SCL (brown) SDA (orange) Ground (blue)

How does this work.

After uploading the code to the two boards, and connecting the boards to the I2C bus, we may power everything up. Please note that the boards MUST have a common ground. I have also powered both from the same supply. also make sure the SDA goes to SDA, and SCL to SCL… On a short distance like this, pull-up resistors are not required ( your milage may vary )

When we first power it up, is will seem as if nothing happened, but if you press and release one of the switches, the LED’s will light up, and stay lit until you press the switch again.

What next ?

In further parts of this, we will expand on this device, turning it into an IoT device, by combining many different skills that I have presented in previous tutorials.