Skip to content

11. Networking and Communications

This week, I explored how microcontrollers can communicate with each other using different protocols.

Understanding the basics

What is Networking and Communications?

Networking and Communications refers to how devices are connected to each other and how through wired or wireless communication they exchange data.

UART

UART (Universal Asynchronous Receiver/Transmitter) is a hardware communication protocol used to transmit data asynchronously — without a clock signal.It requires only two lines: TX (Transmit) and RX (Receive). This is commonly used between microcontrollers and modules like Bluetooth, GPS, etc.

USART

USART (Universal Synchronous/Asynchronous Receiver/Transmitter) is an extension of UART that supports both asynchronous and synchronous communication.

SERCOM

  • SERCOM (Serial Communication Interface) is found in microcontrollers like those from Atmel (e.g., SAMD21). SERCOM modules are flexible — they can be configured as UART, SPI, or I2C, depending on project needs.

PIO

  • PIO (Programmable I/O) is Specific to the RP2040 microcontroller. It allows users to create custom peripheral behavior using software-defined state machines.

Asynchronized Serial Communication

Asynchronized Serial Communication means data is transmitted without a shared clock signal. Instead, both sender and receiver agree on a baud rate (speed), and data is framed with start and stop bits for proper synchronization.

  • RS232: RS232 is used for simple device-to-device communication.
  • RS422: RS422 supports longer distances and multiple receivers, ideal for industrial settings.
  • RS485: RS485 is great for multi-device networks, making it suitable for building automation and industrial LANs.

Serial Communication Standards

Standard Communication Type Direction Devices Supported Max Distance
RS232 Point-to-Point Full Duplex 1-to-1 ~15 meters
RS422 Point-to-Multipoint Full Duplex 1 transmitter, up to 10 receivers ~1200 meters
RS485 Multi-Point (LAN) Half Duplex Multiple transmitters & receivers ~1200 meters

Synchronized Serial Communication

Synchronized Serial Communication involves the exchange of data between devices using a shared clock signal. This ensures both sender and receiver are perfectly synchronized, making data transfer faster and more reliable compared to asynchronous communication.

Two common synchronous communication protocols are:

Serial Peripheral Interface (SPI)

  • A fast communication method used for short distances.
  • Needs 4 wires: MOSI, MISO, SCK, and SS/CS.
  • One Master can talk to many Slaves using separate select lines

I2C (Inter-Integrated Circuit)

  • Uses just 2 wires: SDA (data) and SCL (clock).
  • One Master can talk to many Slaves using unique addresses.
  • Slower than SPI but more efficient for connecting multiple devices.

Individual Assignment

Arduino to Arduino (I2C)

I reffred to Autodesk Instructables I2C Between Arduinos by Cornela to get a better understanding of how it works.

What is I2C? I2C (Inter-Integrated Circuit) is a two-wire communication protocol that allows multiple devices to communicate with a microcontroller over the same bus. It uses:

  • SDA (Serial Data Line) for data transfer
  • SCL (Serial Clock Line) for synchronization

How It Works

  • The Main sends a command to the Secondary using I2C.
  • The Secondary listens at a particular I2C address and takes action.
  • In this example, when the Master sends 1, the Secondary turns ON an LED. When it sends 0, the LED turns OFF.

Wire connections

Connection Description Main Arduino Pin Secondary Arduino Pin
I2C Data Line A4 (SDA) A4 (SDA)
I2C Clock Line A5 (SCL) A5 (SCL)
Common Ground GND GND
LED for Output (Slave) Digital Pin 12 → GND

Code

Uploading the main code

Uploading the secondary code

Note: Remember to check your ports before uploading the code

Main Code

#include <Wire.h>

int slaveAddress = 9;  // Slave I2C address (same as in the slave code)

void setup() {
// Start the I2C communication as master
Wire.begin(); 

// Start the serial communication for debugging
Serial.begin(9600);
 delay(1000);  // Wait for Serial to initialize
}

void loop() {
 // Send a command to the slave to blink the LED
 Wire.beginTransmission(slaveAddress);
 Wire.write('1');  // Send the command '1' to make the slave blink the LED
Wire.endTransmission();

// Print to Serial Monitor
Serial.println("Sent command to blink LED");

// Wait 2 seconds before sending the next command
 delay(2000);
}

Secondary Code

#include <Wire.h>

int LED = 12;  // Pin for the LED
int x = 0;     // Variable to store received data

void setup() {
// Define the LED pin as Output
pinMode(LED, OUTPUT);

// Start the I2C Bus as Slave with address 9
Wire.begin(9); 

// Attach the receive function to handle I2C communication
Wire.onReceive(receiveEvent);

// Start serial communication for debugging
Serial.begin(9600);
}

void receiveEvent(int bytes) {
x = Wire.read();  // Read the byte sent from the master
}

void loop() {
 // Print the received value to Serial Monitor for debugging
Serial.print("Received value: ");
Serial.println(x);

 // If the value received is '1', blink the LED every 1 second
if (x == '1') {
digitalWrite(LED, HIGH);   // Turn the LED on
 delay(1000);                // Wait for 1 second
digitalWrite(LED, LOW);    // Turn the LED off
delay(1000);                // Wait for 1 second
}

// Optional: Small delay to avoid overloading the Serial Monitor
delay(100);
 }

Successful LED Blink

Once the code was uploaded the LED blinked, confirming that the I2C communication was working properly. The setup was stable and successfully demonstrated how two Arduinos can communicate using just two data lines.

Xiao RP2040 to Arduino UNO (I2C)

While working on this assignment, I referred to Siddharth Agarwal's documentation

From his documentation, I understood:

  • RP2040 to RP2040 does not work due to a bug in the chip itself
  • When connecting RP2040 to anything else, to be cautious of the voltage difference.

He quoted a valuable insight from our mentor Jesal sir's that

“One has to be careful here, since the XIAO operates at 3.3 V while the Uno operates at 5 V. The I2C connection operates at whatever the Master is operating at. So the XIAO being the Primary or Master would work for the Uno but not the other way !”

For this I will be using the board that I made during week 8

Wiring Connection

XIAO RP2040 Arduino UNO Description
D4 (SDA) A4 (SDA) I2C Data Line
D5 (SCL) A5 (SCL) I2C Clock Line
GND GND Common Ground
Pin 12 + GND LED connected on UNO

Code

Uploading the main code

Uploading the secondary code

Main Code for Xaio

#include <Wire.h>

int slaveAddress = 9;  // The address of the Arduino Uno (slave)

void setup() {
// Start the I2C communication as master
 Wire.begin(); 

// Start the serial communication for debugging
Serial.begin(9600);
delay(1000);  // Wait for Serial to initialize
}

void loop() {
// Send a command to the slave to blink the LED
Wire.beginTransmission(slaveAddress);
Wire.write('1');  // Send the command '1' to make the slave blink the LED
Wire.endTransmission();

// Print to Serial Monitor
Serial.println("Sent command to blink LED");

// Wait 2 seconds before sending the next command
delay(2000);
}

Secondary Code for Arduino

#include <Wire.h>

int LED = 12;  // Pin for the LED
int x = 0;     // Variable to store received data

 void setup() {
// Define the LED pin as Output
pinMode(LED, OUTPUT);

// Start the I2C Bus as Slave with address 9
Wire.begin(9); 

// Attach the receive function to handle I2C communication
Wire.onReceive(receiveEvent);

// Start serial communication for debugging
Serial.begin(9600);
}

void receiveEvent(int bytes) {
x = Wire.read();  // Read the byte sent from the master
}

void loop() {
// Print the received value to Serial Monitor for debugging
Serial.print("Received value: ");
Serial.println(x);

// If the value received is '1', blink the LED every 1 second
if (x == '1') {
digitalWrite(LED, HIGH);   // Turn the LED on
delay(1000);                // Wait for 1 second
digitalWrite(LED, LOW);    // Turn the LED off
delay(1000);                // Wait for 1 second
}

// Optional: Small delay to avoid overloading the Serial Monitor
delay(100);
}

Promblem faced: The secondary was not responding at all. It was showing error in the code

How I fixed it: I forgot to call Wire.onReceive() in the secondary code! After setting the receive callback properly, everything worked.

Final Working

When the XIAO (Main) receives an input signal, it sends data to the Arduino (Secondary), which produces the corresponding output by lighting up the LED. This shows the input on the Main device triggering the output on the Secondary device.

ESP8266 to ESP8266

Earlier, I was quite confused about this topic. Later during our IoT module wireless communication was taught, Chinmayee Sir explained it in such a simple and clear way that something which once felt complex suddenly made a lot of sense.

The ESP8266 is a low-cost, low-power system-on-chip (SoC) with integrated Wi-Fi and Bluetooth capabilities. It is widely used in IoT (Internet of Things) projects due to its versatile features

Pinout

The ESP8266 has many pins used for different purposes such as GPIO (General Purpose Input/Output), power supply, and communication interfaces. Below is a simplified pinout of the ESP32.

Key Pins on the ESP8266:

  • GPIO Pins (0 to 39): These are the general-purpose input/output pins.

  • 3V3 (3.3V): This pin provides 3.3V output.

  • 5V: Provides 5V output.

  • GND: Ground pin.

  • EN (Enable): Used to enable or reset the ESP32.

  • TX/RX (UART): Pins used for serial communication.

  • SDA/SCL (I2C): Pins for I2C communication.

  • MOSI/MISO (SPI): Pins for SPI communication.

  • Analog Pins (ADC): For analog signals.

Setting up the IDE

In order to program the ESP 8266 we need to download the necessary drivers. You can download them by go to [Silicon Labs] (https://www.silabs.com/developer-tools/usb-to-uart-bridge-vcp-drivers) site. We use a USB to UART bridge. Most ESP8266 boards have the CP210x chip (made by Silicon Labs) built in. This chip helps the computer communicate with the ESP32 using USB. Without downloading the driver yoursystem might not be able to detect the board.

I installed the Universal windows driver and ran the program on my computer.

Board setup on Arduino IDE

Next I opened up Arduino IDE and went to Preferences under File.

Over there, under Additional Boards Manager URL I added the following link: https://arduino.esp8266.com/stable/package_esp8266com_index.json

After that I went to Tools>Board>Board Manager and then sercah for esp32.

And then install the Arduino ESP8266 board

After installation You’ll now be able to select ESP32 boards under: Tools > Board > esp8266> Generic esp8266 Module

Then connect NodeMCU to the port and set the board.

Generating Mac Address

To start with any wireless communication, we need to first find the Mac Address of the microcontroller we are using. Each ESP device has a unique Mac Address required for any sort of wireless communication. Mac Address actually stands for “Media Access Control Address”.

You simply run the Mac Address code after connecting your microcontroller and once uploaded, hit the “Reset” button on your microcontroller and look at your Serial Monitor. You will find the unique 6-figure address of your controller.

Code

#include <ESP8266WiFi.h>
void setup(){
Serial.begin(115200);
delay(500);
Serial.println();
Serial.print("MAC: ");
Serial.println(WiFi.macAddress());
}
void loop(){}

Main Mac Address

Secondary Mac Address

One Way Communication

In this method, we have a main and a secondary device. We can use the main device to send data to the secondary device to execute various operations. I used a push button on the Main device to trigger the LED on the Secondary device.

Connections

Main: Connect the Push Button to pin D7

Secondary: Connect the LED to pin D2

Upload the below main push button code to main Node MCU.

In the Mac address put the Secondary's MAC Address

#include <ESP8266WiFi.h>
#include <espnow.h>

#define BUTTON_PIN D7  // GPIO13

uint8_t receiverMac[] = {0xF4, 0xCF, 0xA2, 0x75, 0x35, 0x66}; // Replace with your receiver's MAC
bool lastButtonState = HIGH;

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  WiFi.mode(WIFI_STA);
  if (esp_now_init() != 0) {
    Serial.println("ESP-NOW init failed!");
    return;
  }

  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
  esp_now_add_peer(receiverMac, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
}

void loop() {
  bool currentState = digitalRead(BUTTON_PIN);
  if (currentState != lastButtonState) {
    lastButtonState = currentState;
    uint8_t msg = (currentState == LOW) ? 1 : 0;  // LOW when pressed (due to INPUT_PULLUP)
    esp_now_send(receiverMac, &msg, sizeof(msg));
  }
  delay(10);
}

Upload the below Secondary code to the secondary NodeMCU

#include <ESP8266WiFi.h>
#include <espnow.h>

#define LED_PIN D2  // GPIO4

void onDataRecv(uint8_t *mac, uint8_t *data, uint8_t len) {
  if (len > 0) {
    digitalWrite(LED_PIN, data[0] == 1 ? HIGH : LOW);
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  WiFi.mode(WIFI_STA);
  if (esp_now_init() != 0) {
    Serial.println("ESP-NOW init failed!");
    return;
  }

  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
  esp_now_register_recv_cb(onDataRecv);
}

void loop() {
  // Nothing needed here
}

Note: While uploading the code, make sure only one NodeMCU is connected to your computer at a time. First, upload the main code to one board. Then disconnect it, connect the second board, and upload the secondary code. Once both codes are uploaded, you can connect both boards simultaneously for communication.

This is important because if both boards are connected at once, your computer may get confused between the two COM ports, and the code might upload to the wrong board or cause errors. Uploading one at a time avoids this issue and ensures smooth communication.

Bi-directional Communication

In this method, we have two devices sharing data with each other i.e. both transmitting and receiving. Such devices are called “transceivers”.

For bi-directional communication, I used the same setup as before—controlling an LED using a switch, but this time there was no main or secondary NodeMCU—both boards ran the same code and could send and receive data from each other. The other key difference was the use of a latching switch. A latching switch stays in its on or off position after being pressed, making it ideal for two-way control.

Make the connection on both the NodeMCU as:

Connect Switch to GND AND D7 and Connect LED to GND AND D2

Upload the below code to both the Node MCU

Make sure to change the Mac Address in both the codes

//----------------------------------------Load libraries
#include <espnow.h>
#include <ESP8266WiFi.h>
//----------------------------------------Defines PIN Button and PIN LED.
#define LED_Pin   D4
#define BTN_Pin   D3
//----------------------------------------------------------------------------------------------------------------------------

//uint8_t broadcastAddress[] = {0xE0, 0x98, 0x06, 0x9C, 0x16, 0x7F}; //--> REPLACE WITH THE MAC Address of your receiver.
uint8_t broadcastAddress[] = {0x2C, 0xF4, 0x32, 0x0E, 0xE0, 0xF8}; //--> REPLACE WITH THE MAC Address of your receiver.
//---------------------------------------------------------------------------------------------------------------------------

int BTN_State; //--> Variable to hold the button state.
int LED_State_Send = 1; //--> Variable to hold the data to be transmitted to control the LEDs.
int LED_State_Receive; //--> Variable to receive data to control the LEDs on the ESP32 running this code.

//----------------------------------------Structure example to send data must match the receiver structure

typedef struct struct_message {
    int led;
} struct_message_send;

struct_message send_Data; // Create a struct_message to send data.
struct_message receive_Data; // Create a struct_message to receive data.
//---------------------------------------------------------------------------------------------------------------------------

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Callback when data is sent

void OnDataSent(uint8_t *mac_addr, uint8_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  if (status ==0){
     Serial.println("Delivery success");
  }
  else{
    Serial.println("Delivery fail");
  }
  Serial.println(">>>>>");
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Callback when data is received

void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {
  memcpy(&receive_Data, incomingData, sizeof(receive_Data));
  Serial.println();
 // Serial.println("<<<<< Receive Data:");
 // Serial.print("Bytes received: ");
 // Serial.println(len);
  LED_State_Receive = receive_Data.led;
  Serial.print("Receive Data: ");
  Serial.println(LED_State_Receive);
  Serial.println("<<<<<");
  digitalWrite(LED_Pin, LED_State_Receive);
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ VOID SETUP
void setup() {
  Serial.begin(115200);
  pinMode(LED_Pin, OUTPUT);
  pinMode(BTN_Pin, INPUT_PULLUP); 
  WiFi.mode(WIFI_STA); //--> Set device as a Wi-Fi Station
  WiFi.disconnect();

  //----------------------------------------Init ESP-NOW
  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  //----------------------------------------Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);
  esp_now_register_recv_cb(OnDataRecv); //--> Register for a callback function that will be called when data is received
   // Set ESP-NOW Role
  esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
//----------------------------------------

  //----------------------------------------Register peer
   if (esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_COMBO, 1, NULL, 0) == 0) {
    Serial.println("Peer added successfully !!");
  } else {
    Serial.println("Failed to add peer !!");  
  }
  Serial.println("Initializing push button ...");
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
void loop() {
  BTN_State = digitalRead(BTN_Pin); //--> Reads and holds button states.

  //----------------------------------------When the button is pressed it will send data to control the LED on the ESP32 Target.
  if(BTN_State == 0) {
    LED_State_Send = !LED_State_Send; // toggle state
    send_Data.led = LED_State_Send;  // send state to the other board
    Serial.println();
    Serial.print(">>>>> ");
    Serial.println("Send data");
    //----------------------------------------Send message via ESP-NOW
  if (esp_now_send(broadcastAddress, (uint8_t *) &send_Data, sizeof(send_Data))==0)
     {
      Serial.println("Message sent successfully");
    }
    else {
      Serial.println("Error sending the data");
    }  
      //----------------------------------------Wait for the button to be released. Release the button first to send the next data.
    while(BTN_State == 0) {
      BTN_State = digitalRead(BTN_Pin);
      delay(10);
    }

  }

}

Group Assignment

This week's group project is uploaded on Mihir's page please refer to that.

Exercise files

Below are the files for:

Arduino to Arduino: Main code

Arduino to Arduino: Secondary code

Xiao RP2040 to Arduino UNO: Main code

Xiao RP2040 to Arduino UNO: Secondary code

NodeMCU Main code

NodeMCU code

Bidirectional code