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 sends0
, 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: Secondary code
Xiao RP2040 to Arduino UNO: Main code