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
- Arduino MEGA2560 board
- USB Cable Type-A/C to Type-B
- ESP8266 D1 Mini Wi-Fi Module
- USB Cable Type-A/C to Type-B
- Breadboard
- 2 Breadboard jumper wires
- Soldering iron and wire
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:
Simplicity: With I2C, only two wires are needed for communication, simplifying the hardware setup and reducing wiring complexity in our car.
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:
- Soldering Tutorial for Beginners: Five Easy Steps - YouTube
- 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:
The Arduino has the SDA and the SCL pins above the reset button.
Steps to connect:
- Connect the Arduino to the computer using Type-B cable
- Connect the ESP8266 to the computer using Type-C cable
- Connect D1 (SCL) on the ESP8266 with the SCL pin on Arduino
- Connect D2 (SDA) on the ESP8266 with the SDA pin on Arduino
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.
Board Manager: Type
Ctrl+Shift+P
to open Command Palette in VSC and then search forArduino: Board Manager
Within the Board Manger, search for
esp8266
and install the package shown below
Settings.json: Type
Ctrl+P
to open a specific file in VSC and then search forsettings.json
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"
]
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
- Create a new directory called
arduino-car
- Within the
arduino-car
directory, create two new directories calledarduino
andesp8266
- Within the
arduino
directory:- Create a sketch file named
arduino.ino
- Create a directory
src/Secondary
with new Secondary.h header file and Secondary.cpp implementation file.
- Create a sketch file named
- Within the
arduino
directory:- Create a sketch file named
arduino.ino
- Create a directory
src/Secondary
with new Secondary.h header file and Secondary.cpp implementation file.
- Create a sketch file named
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.
-
Serial Monitor:
- It's a tool for monitoring Arduino output, debugging code, and interacting with projects in real-time
-
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
-
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);
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
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();
}
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
}
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
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 8We bind the functions for responding to request and receiving data using the
Wire.onRequest()
andWire.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();
}
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); }
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.
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
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.
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!