Skip to content

11. Enable devices to talk to each other

This week I learnt about the different communication protocols that enable devices to to talk to each other. Specifically, I learnt about how an I2C bus can enable many “Child” (formerly “Slave”) devices to communicate with a “Master” device.

I wasn’t able to try Wireless communication such as Wifi and Bluetooth this week, but I hope to also implement them in my Final Project.

This week's assignments (Apr 1 - Apr 8):

Group assignment:
- Send a message between two projects

Individual assignment:
- Design, build and connect wired or wireless node(s) with network or bus addresses and a local input and/or output devices

Groupwork: Send a message between two projects

Our task for this week was to communicate between any 2 devices we already made.
We decided to connect Shoko and Maki’s projects using 2 wireless methods; Wireless and Bluetooth. Because both boards were ESP32s (Xiao), we could use ESP-NOW, which is a protocol developed by Espressif for ESP32 chips. It enables quick, low-power, and low-latency peer-to-peer data exchange, without the need for a router.
You can find the group assignment on the below page.
Link to Groupwork documentation

Individual work: Design build and connect a node with other devices

1. Basics of Communication

Data transfer can be done via Wired methods like USB and I2C, or Wireless methods, like Wi-Fi and Bluetooth. 

  • Wired connections: can be Parallel (multiple bits of data are simultaneously sent over multiple wires), or Serial (data is sent bit by bit over a single or few wires).
    Serial communications can furthermore be Synchronous (makes use of a clock line), or Asynchronous. Typically the different protocols are characterised by their range (Distance), bandwidth (Speed), data packet length, ability to connect multiple devices, etc.
  • Wireless connections transmit data through radio waves, offering mobility, but often lower speeds and shorter ranges than wired alternatives. Similarly to Wired connections, different types of connections differ in terms of their bandwidth and their range. For example, Wi-fi has high range but low bandwith, 5G or Lora have high range and high bandwidth, while RFID or Bluetooth have relatively low range and low bandwidth

This week, I decided to connect different shelves of my rack using Wired communication. My plan was to have 1 master board, and separate child boards for each row of shelf. Rico recommended me to use I2C (Inter Integrated Circuits) for this, but since I couldn’t understand exactly why, I did some research into the different Wired communication protocols and what sets I2C apart.

👉Communication Protocols are standardized sets of rules that govern how data is exchanged between devices, ensuring they can transmit, receive, and interpret information correctly. They define the data format, transmission method (such as which device speaks first, at what point, etc), etc. Google AI Overview

Common Wired Communication Protocols:

alt text

Above research helped me understand the advantages of I2C; in that it enables me to communicate with many devices using just 2 common lines, plus Ground and and Vcc. I also learnt that I need to add an external Pull-up resistors to achieve stable readings.

👉Pullup Resistors for I2C:
To get stable readings on an I2C bus, we typically need to add external pull-up resistors; one for SDA and one for SCL, pulled up to VCC (3.3V). While ESP32 has internal pull-up resistors, their resistance is often too high for reliable I2C communication, particularly at higher speeds.
The ideal resistor value depends on the required bus speed, voltage, and total bus capacitance, which is a sum of wires and all connected devices. Typically a range between 2kΩ to 10kΩ is recommended, with a common starting point of 4.7kΩ for ESP32. (Ref: Electrical Engineering Stack Exchange)

2. Setting realistic goals

After my learnings from last week, I first started with scoping realistic objectives for this week;
Main goal: Learn how a Master device can and recognise different inputs from peripheral devices.
Stretch goal: Learn how a Master device can control LED output on other child devices.

3. Implementing I2C

I decided to start with a simple button recognition circuit, and expand the features bit by bit
alt text

3.1 Read button status without I2C

I connected 2 buttons (Red and Blue) to my ESP32C3, and wrote and tested the code below for reading button status.

Code const int buttonPinRed = D0; // the number of the pushbutton pin
const int buttonPinBlue = D2; // the number of the pushbutton pin

// variables will change:
int buttonStateRed = 0; // variable for reading the pushbutton status
int buttonStateBlue = 0; // variable for reading the pushbutton status

void setup() {
// initialize the pushbutton pin as an input:
Serial.begin(9600);// initialize serial communication at 115200 bits per second
pinMode(buttonPinRed, INPUT_PULLUP);
pinMode(buttonPinBlue, INPUT_PULLUP);
}

void loop() {
// read the state of the pushbutton value:
buttonStateRed = digitalRead(buttonPinRed);

// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
if (buttonStateRed == HIGH) {
// turn LED on:
Serial.print("Red is on, ");
} else {
// turn LED off:
Serial.print("Red is off, ");
}
// read the state of the pushbutton value:
buttonStateBlue = digitalRead(buttonPinBlue);

// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
if (buttonStateBlue == HIGH) {
// turn LED on:
Serial.println("Blue is on");
} else {
// turn LED off:
Serial.println("Blue is off");

}
}

3.2 Test I2C (Simple Serial communication)

Since I need 2 PCBs for communication, I fabricated another Development board which is almost the same as the one from Week 8, but with 4.7kΩ Pull-up Resistors on SDA and SCL lines. I then connected the 2 boards via the 2 I2C lines, VCC and Ground.

Then I deployed the Wire library example provided on Arduino to first test that the I2C communication works properly.

Master device #include "Wire.h"

#define I2C_DEV_ADDR 0x55 //Specifies the slave device address

uint32_t i = 0;

void setup() {
Serial.begin(115200);//Initiate Serial communication
Serial.setDebugOutput(true);
Wire.begin();
}

void loop() {
delay(5000);

//Write message to the slave
Wire.beginTransmission(I2C_DEV_ADDR);
Wire.printf("Hello World! %lu", i++);
uint8_t error = Wire.endTransmission(true);
Serial.printf("endTransmission: %u\n", error);

//Read 16 bytes from the slave
uint8_t bytesReceived = Wire.requestFrom(I2C_DEV_ADDR, 16);
Serial.printf("requestFrom: %u\n", bytesReceived);
if ((bool)bytesReceived) { //If received more than zero bytes
uint8_t temp[bytesReceived];
Wire.readBytes(temp, bytesReceived);
log_print_buf(temp, bytesReceived);
}
}
Follower device #include "Wire.h"
#define I2C_DEV_ADDR 0x55 //Declare the device's unique address to Master board

uint32_t i = 0;//start with uint32_t = 0

void onRequest() {//Define the onRequest function
Wire.print(i++);//Every time request is received, increase i by 1
Wire.print(" Packets.");
Serial.println("onRequest");
}

void onReceive(int len) {//Define the onReceive function
Serial.printf("onReceive[%d]: ", len);
while (Wire.available()) {
Serial.write(Wire.read());
}
Serial.println();
}

void setup() {
Serial.begin(115200);//Initiate Serial communication
Serial.setDebugOutput(true);
Wire.onReceive(onReceive);//Execute onReceive
Wire.onRequest(onRequest);//Execute onRequest
Wire.begin((uint8_t)I2C_DEV_ADDR);

#if CONFIG_IDF_TARGET_ESP32
char message[64];
snprintf(message, 64, "%lu Packets.", i++);
Wire.slaveWrite((uint8_t *)message, strlen(message));
#endif
}

void loop() {}

3.3 Read Input of another device via I2C

I connected my first Pushbutton circuit to my Child I2C device, which is itself connected to my Master device via I2C.
Then, with help from my instructors, I wrote a code for the Master device to read button status of the Child I2C device.

Master device #include "Wire.h"
#define I2C_DEV_ADDR1 0x55

uint32_t i = 0;

void setup() {
Serial.begin(115200);
Serial.setDebugOutput(true);
Wire.begin();
}

void loop() {
delay(5000);

//Write message to the slave
Wire.beginTransmission(I2C_DEV_ADDR1);//Initiate transmission
Wire.printf("Hello device1! %lu", i++);
uint8_t error = Wire.endTransmission(true);
Serial.printf("endTransmission: %u\n", error);

//Read 16 bytes from the slave
uint8_t bytesReceived = Wire.requestFrom(I2C_DEV_ADDR1, 2);
Serial.printf("requestFrom: %u\n", bytesReceived);
if ((bool)bytesReceived) { //If received more than zero bytes
uint8_t temp[bytesReceived];
Wire.readBytes(temp, bytesReceived);
log_print_buf(temp, bytesReceived);
}
}
Follower device #include "Wire.h" #define I2C_DEV_ADDR 0x55 uint32_t i = 0; const int buttonPinRed = D0; const int buttonPinBlue = D2; int buttonStateRed = 0; // variable for reading the pushbutton status int buttonStateBlue = 0; // variable for reading the pushbutton status void onRequest() { // Wire.print(i++); buttonStateRed = digitalRead(buttonPinRed);//read the buttonPinRed and assign status if (buttonStateRed == HIGH) { // check if red buttonState is HIGH: Wire.write(1);//If High, Send 1 to Master device //Wire.print(" Packets."); Serial.println(1);//and print 1 on Serial Monitor } else { Wire.write(2);//If not, send 2 to Master device Serial.println(2);//likewise } Serial.println("OnRequest"); } void onReceive(int len) { Serial.printf("onReceive[%d]: ", len); while (Wire.available()) { Serial.write(Wire.read()); } Serial.println(); } void setup() { Serial.begin(115200); pinMode(buttonPinRed, INPUT_PULLUP);//Set Red pin button as Pullup Input pinMode(buttonPinBlue, INPUT_PULLUP); Serial.setDebugOutput(true); Wire.onReceive(onReceive);//Execute Wire.onReceive Wire.onRequest(onRequest);//Execute Wire.onRequest Wire.begin((uint8_t)I2C_DEV_ADDR);//Initialize the Wire library and joins the I2C bus as a slave #if CONFIG_IDF_TARGET_ESP32 char message[64]; snprintf(message, 64, "%lu Packets.", i++); Wire.slaveWrite((uint8_t *)message, strlen(message)); #endif } void loop() {}

3.4 Read Child Device’s Resistor values via I2C

From this point forward, I did not have the instructors beside me, so I did a lot of research to understand the various programming commands for sending data via I2C.

  1. Include the wire.h library: Always start by including the I2C library.
  2. For a master device requesting for data, below needs to be included inside void setup();
    Wire.begin(address): Initiate the Wire library and join the I2C bus as a master (no address declared) or slave.
    Wire.beginTransmission(address):
    → To begin a transmission to the I2C device with the given slave address.
    Wire.RequestFrom(address, number of bytes) to request for specified bytes from specified address.
    → then Wire.read(): to read a byte that was received from the specified device.
    Wire.endTransmission();
    → To end a transmission to a slave device that was begun by beginTransmission() and transmits the bytes that were queued by Wire.write().
  3. For a child device to send data to the master, below needs to be included;
    Wire.write(): to write data. (in-between calls to Wire.beginTransmission() and Wire.endTransmission().) Can be written as;
    → Wire.write(value): to send a single byte.
    → Wire.write(string): to send a series of bytes.
    → Wire.write(data, length): data: an array of data to send as bytes, length: the number of bytes to transmit.
    Wire.onRequest();
    → The function to run when there is a request for data. (using Wire.requestFrom())
    We can include Wire.onRequest(RequestEvent);// inside void setup, and then create another block void RequestEvent().
    → Inside this, we can include the Wire.write() function.
    → Although I will not be using this, Wire.onReceive(); can be used for receiving data from the master, alongside Wire.read(); function.
  4. Initially I hoped to send 12 bits, but with Serial communication, I can only send 1 byte (8 bits) at a time. Therefore for this assignment I set the Analog.read resolution to 8-bits.

After a few weeks of above research, I was able to complete a working code:

Codes for Master device //I2C Master - Program for getting Analog Read values from Child #include

const uint8_t SHELF_ADDR01 = 0x08; //Define address as an 8 bit variable

void setup() {
Wire.begin(); //Initiate I2C. No address declared means initialise as the Master device.
Serial.begin (4095);
}

void loop() {
int8_t Res; //Res is the final 16 bit variable
Wire.beginTransmission(SHELF_ADDR01);
Wire.endTransmission(false);
Wire.requestFrom(SHELF_ADDR01, 1); //Specifies how many bytes to read..
byte Res = Wire.read(); //Read register0x00 and store it in Res
Serial.print(Res);
Serial.println(",");
delay(1000);
}
Codes for Child device //I2C Child - Program for getting sending Analog Read values to Master

#include
#define SHELF_ADDR01 0x08 //Assign Shelf Address
int InputTag = D0;
int Res;

void setup() {
analogReadResolution(8); //Set ADC resolution to 12 bits (max on esp32c3)
pinMode(InputTag, INPUT); //Set Red pin button as Input
Wire.begin(SHELF_ADDR01); //Declare my address
Wire.onRequest(RequestEvent);//Function to run upon onRequest (when asked for data?)
//Wire.onReceive(ReceiveEvent);//Function to execute upon onReceive (when receiving data)
Serial.begin(4095);
}

void loop() {
}

void RequestEvent(){ //When master requests data from child
int TagValue = analogRead(A0); //Read potentiometer value from pin A0
Wire.write(TagValue); Send the 8-bit value to Master device
Serial.print("TagValue: ");
Serial.println(TagValue);//
}

alt text

3.5 Request another device to turn on LED

I could not try this stretch goal, but I will probably deploy this in my Final Project.

Files

Reflections:

This week I learnt about the variety of different communication protocols that enable devices to reliably talk to each other.

After last week’s learnings, I tried to adjust my learning objectives to realistic levels this week. But since I am new to programming, and because I did not have instructors to help me after June, understanding all the I2C-related commands turned out to be more tricky than I expected. It actually ended up taking me a few weeks to get a working code, and successfuly send a few bytes from the child device to Master device.

Further Questions:
To understand this topic better, I would like to learn about the Pulses of Serial communication and the use of Oscilloscopes to measure them. I’d also like to learn about Electromagnetic Waves, to understand Wireless communication mechanisms better. For my final project, I would also like to build an easily scalable I2C bus network system in order to make full use of the maximum number of connectable devices.

Assignment Checklist:

  • Linked to the group assignment page
  • Documented my project and what I have learned from implementing networking and/or communication protocols
  • Explained the programming process(es) I used
  • Ensured and documented that my addressing for boards works
  • Outlined problems and how I fixed them
  • Included design files (or linked to where they are located if I am using a board I have designed and fabricated earlier) and original source code