Communication b/w Arduino and ESP8266📶

Samreen Ansari - Mar 4 - - Dev Community

Introduction

Last time, we set up VSC for Arduino development and wrote code for making the LED on the Arduino blink. Today, we will see how to communicate between the Arduino and the ESP8266 Wi-FI module.
Why do we need to do this? In our Arduino car project, we'll perform computations on the computer and send instructions wirelessly to the Arduino via Wi-Fi. Since conventional Arduinos lack built-in Wi-Fi capability, we rely on external Wi-Fi modules to facilitate this communication.

Table of Contents

 1. Introduction
 2. Table of Contents
 3. The Components
 4. Communication Protocols
      4.1 Synchronous vs. Asynchronous
      4.2 Serial vs. Parallel
      4.3 Common Protocols
 5. Communication between the Arduino and ESP8266
      5.1 The ESP8266 Wi-Fi Module
      5.2 Getting the ESP8266 ready to connect to the Arduino
      5.3 Wiring
      5.4 Installing the board manager for ESP8266
      5.5 Implementing I2C Communication in Code
            5.5.1 Creating the directory structure
            5.5.2 Writing the code
            5.5.3 Uploading the code to the devices
            5.5.4 Monitoring the output
            5.5.5 Troubleshooting
 6. What's Next
 7. Resources

The Components

  1. Arduino MEGA2560 board
  2. USB Cable Type-A/C to Type-B
  3. ESP8266 D1 Mini Wi-Fi Module
  4. USB Cable Type-A/C to Type-B
  5. Breadboard
  6. 2 Breadboard jumper wires
  7. Soldering iron and wire

Components

Communication Protocols

Embedded electronic components use communication protocols to share information between them.
Communication protocols are of different kinds (Read more at Sparkfun):

1. Synchronous vs. Asynchronous

Synchronous communication involves synchronized timing between the sender and receiver. Both parties must agree on the timing of data transmission. It involves a clock signal along with the data signal.
Asynchronous communication does not rely on synchronized timing. Instead, data is transmitted with start and stop bits, allowing for more flexible timing.

2. Serial vs. Parallel

Serial communication transmits data one bit at a time over a single wire or channel. It's simpler and more cost-effective for long-distance communication.
Parallel communication transmits multiple bits simultaneously over separate wires or channels. It offers higher data transfer rates but requires more wires and is prone to signal degradation over longer distances.

3. Common Protocols

  • UART (Universal Asynchronous Receiver-Transmitter): Used for asynchronous serial communication between devices. Commonly used for simple point-to-point communication.
  • SPI (Serial Peripheral Interface): Allows full-duplex synchronous serial communication between microcontrollers and peripheral devices. Ideal for high-speed communication with short distances.
  • I2C (Inter-Integrated Circuit): A synchronous serial communication protocol that allows communication between multiple devices using only two wires (SDA: serial data and SCL: serial clock). It supports multiple devices connected to the same bus, making it suitable for intra-board communication and communication between different components.

Communication between the Arduino and ESP8266

For this project, I chose I2C communication protocol due to its:

  1. Simplicity: With I2C, only two wires are needed for communication, simplifying the hardware setup and reducing wiring complexity in our car.

  2. Efficiency: I2C's synchronous nature ensures precise timing and efficient data transfer, making it ideal for real-time communication.

The ESP8266 WiFi Module

The ESP8266 is a versatile and cost-effective Wi-Fi module that lets microcontrollers like Arduino connect to wireless networks. It acts as a bridge between the Arduino and the internet, allowing the Arduino to communicate with other devices and services over Wi-Fi.

Getting the ESP8266 ready to connect to the Arduino

Some ESP8266 modules might already come soldered with the header pins, which let us use the ESP8266 on a breadboard. In my case, it wasn't already soldered. As I had never soldered before, I used the videos below as a reference before soldering:

  1. Soldering Tutorial for Beginners: Five Easy Steps - YouTube
  2. Solder Header Pins onto your Wemos D1 Mini Esp8266 Arduino - YouTube

Issues I faced while soldering:

  • My solder would not get hot enough. I recommend using a better soldering iron instead of the $10 one I bought lol
  • The solder tip kept getting covered by the solidified metal and hence could not reach the point where I wanted to solder
  • I accidentally soldered two adjacent points together!

I had a desoldering pump and could remove the incorrect solders after a few tries. Soldering was wayyyy trickier than I expected, but I believe it will be better the next time I try it.

Wiring

As we chose I2C communication, we only need two wires between the Arduino and ESP8266.
The image below shows the pin layout of the ESP8266 module:

ESP8266 Pin Layout
Image by https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/

The Arduino has the SDA and the SCL pins above the reset button.

Steps to connect:

  1. Connect the Arduino to the computer using Type-B cable
  2. Connect the ESP8266 to the computer using Type-C cable
  3. Connect D1 (SCL) on the ESP8266 with the SCL pin on Arduino
  4. Connect D2 (SDA) on the ESP8266 with the SDA pin on Arduino

Wiring

Installing the board manager for ESP8266

Before the begin writing our code, we need to set up VSC for the ESP8266. You can find similar steps for the Arduino IDE under Resources.

  1. Board Manager: Type Ctrl+Shift+P to open Command Palette in VSC and then search for Arduino: Board Manager
    Board Manager

  2. Within the Board Manger, search for esp8266 and install the package shown below
    esp8266

  3. Settings.json: Type Ctrl+P to open a specific file in VSC and then search for settings.json
    Settings.json

  4. Within settings.json, add this code at the end of the JSON and then save:

  "arduino.additionalUrls": [
      "http://arduino.esp8266.com/stable/package_esp8266com_index.json"
  ]
Enter fullscreen mode Exit fullscreen mode

Settings ESP

Implementing I2C Communication in Code

In I2C communication, there is typically a primary device and one or more secondary devices. The primary device initiates communication by sending requests or instructions, and the secondary devices respond accordingly. In this case, the ESP8266 will be the primary (as it can't be secondary) and the Arduino will be the secondary.

Creating the directory structure

  1. Create a new directory called arduino-car
  2. Within the arduino-car directory, create two new directories called arduino and esp8266
  3. Within the arduino directory:
    1. Create a sketch file named arduino.ino
    2. Create a directory src/Secondary with new Secondary.h header file and Secondary.cpp implementation file.
  4. Within the arduino directory:
    1. Create a sketch file named arduino.ino
    2. Create a directory src/Secondary with new Secondary.h header file and Secondary.cpp implementation file. Project structure

Writing the code

Before we begin, let's discuss the Serial Monitor, baud rate, and Serial.print() function. These are essential tools in Arduino development for communicating with your computer and debugging your code.

  1. Serial Monitor:
    • It's a tool for monitoring Arduino output, debugging code, and interacting with projects in real-time
  2. Baud rate
    • This determines the speed of communication in bits per second between Arduino and the Serial Monitor.
    • Match the baud rate in your code (Serial.begin()) with the one in the Serial Monitor to avoid garbled or incorrect data
  3. Serial.print():
    • It is used to send data from the Arduino to the Serial Monitor for display.
    • Example:
int sensorValue = 123;
Serial.print("Sensor value: ");
Serial.println(sensorValue);
Enter fullscreen mode Exit fullscreen mode
Primary

First, let us write the code for the primary device - ESP8266. There are comments and docstrings in the code for better understanding.

Primary.h

  • This creates a header file where we declare the functions and the variables

  • We use the Wire library for the I2C communication between the modules

  • We need the address of the secondary module here which will be defined when initializing the secondary

  • We have functions for requesting data from and sending data to secondary

#ifndef PRIMARY_H
#define PRIMARY_H

#include <Wire.h>
#include <Arduino.h>

/**
 * @brief The Primary class functions for the ESP8266 sketch.
 * @author Samreen Ansari
 *
 * This class provides functionality to set up the I2C communication.
 */
class Primary {
 public:
  /**
   * @brief Constructs a new Primary object.
   */
  Primary();

  /**
   * @brief Initializes the I2C communication for sending and receiving data.
   */
  void initialize_i2c();

  /**
   * @brief Requests data from the secondary module.
   */
  void request_data_from_secondary();

  /**
   * @brief Sends data to the secondary module.
   *
   * @param data The data to be sent.
   */
  void send_data_to_secondary(const char* data);

 private:
  /** The I2C address of the secondary module. */
  const int SECONDARY_ADDRESS = 8;
};

#endif  // PRIMARY_H
Enter fullscreen mode Exit fullscreen mode

Primary.cpp

  • Here we implement our header file Primary.h

  • Since this is our primary module, we do not need to specify the address when using Wire.begin()

/**
 * @file Primary.cpp
 * @brief Implementation file for the Primary class.
 * @author Samreen Ansari
 */
#include "Primary.h"

Primary::Primary() {}

void Primary::initialize_i2c() {
  // Begin the I2C communication (primary does not need to specify address).
  Wire.begin();
  delay(100); // add delay to give time for initialization
  Serial.println("I2C Primary Initialized");
}

void Primary::request_data_from_secondary() {
  // Request 21 bytes of data from the secondary device and print it.
  Wire.requestFrom(SECONDARY_ADDRESS, 21);
  // While communication is available,
  // keep reading each character and printing
  while (Wire.available()) {
    char c = Wire.read();
    Serial.print(c);
  }
  Serial.println();
}

void Primary::send_data_to_secondary(const char* data) {
  // Send data to the secondary device.
  size_t len = strlen(data);
  Wire.beginTransmission(SECONDARY_ADDRESS); // begin sending data
  Wire.write((uint8_t*)data, len); // write data of len in format uint8
  Wire.endTransmission();
}
Enter fullscreen mode Exit fullscreen mode

esp8266.ino

In the sketch file, we use the functions from Primary and request data from secondary, as well as send some data to secondary.

Notice the baud rate of 115200 here

/**
 * @file esp8266.ino
 * @brief Arduino code for communication with the WIFi module.
 * @author Samreen Ansari
 *
 * This code initializes a I2C Communication between the Arduino and the
 * ESP8266. The ESP8266 sends data to the Arduino and requests data from the
 * Arduino.
 */

#include "src/primary/Primary.h"

Primary primaryDevice; // create an object of the primary class

/**
 * @brief Initializes the ESP8266 primary WiFi module.
 *
 * This function initializes the I2C communication with the secondary device.
 */
void setup() {
  Serial.begin(115200); // Set the baud rate to monitor serial communication
  primaryDevice.initialize_i2c();
}

/**
 * @brief The main loop of the ESP8266 primary WiFi module.
 *
 * This function is called repeatedly send data to the secondary device,
 * and request data from the device.
 */
void loop() {
  primaryDevice.request_data_from_secondary(); // request data
  delay(1000); // wait for 1 second
  primaryDevice.send_data_to_secondary("from primary to secondary!"); send data
  delay(1000); // wait for 1 second
}

Enter fullscreen mode Exit fullscreen mode
Secondary

Now, we look at the code for the secondary device - Arduino MEGA2560.

Secondary.h

  • This creates a header file where we declare the functions and the variables

  • We have functions for responding to requests and receiving data from primary

#ifndef SECONDARY_H
#define SECONDARY_H

#include <Wire.h>
#include <Arduino.h>

/**
 * @brief The Secondary class functions for the Arduino sketch.
 * @author Samreen Ansari
 *
 * This class provides functionality to set up the I2C communication.
 */
class Secondary {
 public:
  /**
   * @brief Constructs a new Secondary object.
   */
  Secondary();

  /**
   * @brief Initializes the I2C communication for sending and receiving data.
   */
  void initialize_i2c();

  /**
   * @brief Sends data to the primary device when requested.
   */
  static void respond_data_to_primary();

  /**
   * @brief Receives data from the primary device.
   *
   * @param byteCount The number of bytes to receive.
   */
  static void receive_data_from_primary(int byteCount);

 private:
  /** The I2C address of the Arduino. */
  const int SECONDARY_ADDRESS = 8;
};

#endif
Enter fullscreen mode Exit fullscreen mode

Secondary.cpp

  • Here we implement our header file Secondary.h

  • Since this is our secondary module, we need to specify the address here in Wire.begin(), we use 8

  • We bind the functions for responding to request and receiving data using the Wire.onRequest() and Wire.onReceive() functions. We need the functions to be static in order for this to work

/**
 * @file Secondary.cpp
 * @brief Implementation file for the Secondary class.
 * @author Samreen Ansari
 */
#include "Secondary.h"

Secondary::Secondary() {}

void Secondary::initialize_i2c() {
  // Begin the synchronous I2C communication with the specified address.
  Wire.begin(SECONDARY_ADDRESS);

  // Register the functions to be called when data is requested or received.
  Wire.onRequest(respond_data_to_primary);
  Wire.onReceive(receive_data_from_primary);

  delay(100);
  Serial.println("I2C Secondary Initialized");
}

void Secondary::respond_data_to_primary() {
  // Send a response to the primary device when requested.
  const char *message = "Hello from secondary!";
  size_t messageSize = strlen(message);
  // Respond with message of messageSize in format uint8.
  Wire.write((const uint8_t *)message, messageSize);
}

void Secondary::receive_data_from_primary(int byteCount) {
  // Receive data and print from the primary device whenever sent.
  while (Wire.available()) {
    char c = Wire.read(); // read each character and print it
    Serial.print(c);
  }
  Serial.println();
}
Enter fullscreen mode Exit fullscreen mode

arduino.ino

In the sketch file, we use the function from Secondary to initialize the I2C on the Arduino.

Notice the different baud rate of 9600 here

/**
 * @file arduino.ino
 * @brief Arduino Sketch for the Arduino Secondary Device
 * @author Samreen Ansari
 *
 * This sketch initializes the Arduino secondary device, sets up the serial
 *communication, and initializes the I2C communication for the secondary device.
 **/
#include <Wire.h>

#include "src/secondary/Secondary.h"

Secondary secondaryDevice;

/**
 * @brief Initializes the Arduino secondary device.
 *
 * This function sets up the serial communication and initializes the I2C
 * communication for the secondary device.
 */
void setup() {
  Serial.begin(9600);
  secondaryDevice.initialize_i2c();
}

/**
 * @brief The main loop of the Arduino secondary device.
 *
 * This function is called repeatedly to add a delay of 100 milliseconds.
 */
void loop() { delay(100); }

Enter fullscreen mode Exit fullscreen mode

Uploading the code to the devices

Remember to change the Sketch File, Board, and Serial Port from VSC Status Bar and upload the arduino.ino to the Arduino, and upload the esp8266.ino to the ESP8366.

ESP8266:
ESP settings
Arduino:
Arduino settings

Monitoring the output

If we had no errors and successfully uploaded the code to the respective devices, we can open Serial Monitor to view the outputs.

To open it in VSC, press Ctrl+Shift+P and search for Serial Monitor: Focus on Serial Monitor View
Open serial monitor

Here I have two Serial Monitors open, one set to monitor the Arduino on COM8 with baud rate of 9600, and the other to monitor the ESP8266 on COM11 with baud rate 115200.

Serial Monitors

And now we have two-way communication working between the Arduino and the ESP8266. We can modify what kind of data to send and what to do with the received data.

Troubleshooting

  • Error Messages: If you encounter error messages during the upload process, carefully read and understand the error messages displayed in the Arduino IDE. These messages often provide valuable clues about the nature of the problem.

  • USB Driver Issues: Sometimes, driver issues can prevent the Arduino IDE from communicating with the connected devices. Ensure that the necessary USB drivers are installed correctly for your Arduino and ESP8266 modules.

  • Reset Devices: If the devices seem unresponsive or stuck during the upload process, try resetting both the Arduino and ESP8266 modules. This can help resolve temporary glitches or communication errors.

  • Recheck connections: Ensure that the SCL pin on Arduino is connected to D1 on ESP8266 and the SDA pin is connected to the D2 pin, and that the devices are connected to the computer

What's Next

Next time we will see how to communicate between the computer and the Arduino wirelessly.


About Me

I'm a Software Engineer by profession and tinkerer by passion. I love everything AI, Robotics, and embedded systems. While I may be new to embedded programming, I am super excited about this project and can't wait to watch my car go zooooom! 🚗🔌🤖

Check out the code on my GitHub.
Connect with me on LinkedIn.

Happy tinkering!


Resources

. . .