Introduction: Master-Slave Communication Between Two Arduino Uno Boards

About: I'm an avid Arduino and electronics enthusiast with a passion for tinkering, experimenting, and bringing innovative ideas to life.

In this project, we'll explore how to establish master-slave communication between two Arduino boards. Master-slave communication allows one Arduino board (master) to control or communicate with another Arduino board (slave). This can be incredibly useful in various applications, such as home automation, robotics, and sensor networks. By the end of this tutorial, you'll have a solid understanding of how to set up master-slave communication between two Arduino Uno boards.

In our setup, one Arduino board will act as the master, initiating communication and sending commands, while the other Arduino board will serve as the slave, receiving commands and executing them.

Supplies

Arduino UNO R3 - 2

LCD - 1

Potentiometer - 2

220 Ω Resistance - 1

Bread-board - 1

LED - 1

100 Ω Resistance - 1

DHT11 Temperature And Humidity Senso - 1

Power adapter 12V - 1

Power adapter 5V - 1

L293D - 1

Ceramic Capacitor - 1

Connecting Wires

DC Motor - 1

DC Fan - 1

USB A to B cable - 1

Step 1: Understanding Master Slave Communication

In a master-slave communication setup between two Arduino boards, one serves as the master while the other functions as the slave. The master board issues commands to the slave board, directing it to execute specific tasks, and the slave board carries out these instructions, responding back to the master as needed.

This communication typically occurs via Serial UART Communication, utilizing three pins: Rx (receive), Tx (transmit), and GND (ground). In this particular project, the master Arduino sends commands to the slave Arduino through the Serial Tx Pin and receives responses via the Serial Rx Pin.

The commands sent by the master Arduino include:

  1. Requesting temperature data from the slave board.
  2. Requesting humidity data from the slave board.
  3. Requesting the analog value read from a sensor connected to the slave board's A0 input.
  4. Sending instructions to turn the LED on or off based on the analog input value.

Upon receiving these commands, the slave Arduino processes them accordingly:

  • It retrieves temperature and humidity readings from a DHT11 sensor.
  • It reads the analog value from a 10K POT connected to one of its analog input pins.

Once the slave has collected all necessary data, it sends the responses back to the master Arduino.

The master Arduino then displays this collected data on an LCD screen, providing a user-friendly interface for monitoring temperature, humidity, and other relevant information.

Step 2: Setting Up the Master Arduino

In this setup, we are configuring a Master Arduino board to control both a 16x2 LCD display and a motor using an L293D H-bridge motor driver IC. By connecting the LCD in 4-bit mode and utilizing a potentiometer for contrast control, we ensure efficient use of Arduino pins and optimal display performance. Additionally, the motor is powered externally to prevent overloading the Arduino board, ensuring stable operation.


Connection between LCD Side Pin and Arduino 1 Side/Bread-board

VSS/GND (Pin 1) - Connected to Ground

VDD/VCC (pin 2) - Connected to 5VVEE/Vo (Pin 3)Connected to Variable pin of 10k POT to Control Contrast of LCD

RS (Pin 4) - Connected to pin 12

R/W (pin 5) - Connected to Ground

E (Pin6) - Connected to pin 11

D0 (pin 7) - Connected to Ground

D1 (pin 8) - Connected to Ground

D2 (pin 9) - Connected to Ground

D3 (pin 10) - Connected to Ground

D4 (pin 11) - Connected to pin 9

D5 (pin 12) - Connected to pin 8

D6 (pin 13) - Connected to pin 7

D7 (pin 14) - Connected to pin 6

LED(+)(Pin 15) - Connected to VCC via 220ohm resistor

LED(-)(Pin 16) - Connected to Ground


Connection between L293D and Arduino

Enable 1 - Pin 3 of Arduino 1

Input 1 - Connected to 5V

Output 1 - Connected to Motor

Gnd - Connected to Ground

Gnd - Connected to Ground

Output 2 - Connected to Motor or It can be N/C

Input 2 - Connected to Ground

Motor Supply - Connected to 5V pin of External Power Adapter

Vcc - Connected to Vcc Pin of Arduino 1

Pin 0(Rx) of the Master Arduino is connected to the Pin 1(Tx) of the Slave Arduino and Pin 1 (Tx) of the Master Arduino is connected to the Pin 0(Rx) of the slave Arduino.

Step 3: Slave Side Connection

The Slave is used to interface with a DHT11 Temperature and Humidity sensor, with its data pin linked to pin 2. A 10k potentiometer is attached to the A0 analog input pin of the Slave board.

On the Master board, an LED is wired to pin 8, its state manipulated by commands from the Master board.

Connection between Arduino 2 to Arduino 1

pin 0 - pin 1 of Arduino 1

pin 1 - pin 0 of Arduino 1

pin 2 - Connected to Data pin of the Temperature Sensor

pin 8 - Negative Side of LED

A0 - Variable pin of POT

5V - Positive pin of LED and One side of POT

Gnd - Another Side of POT

Step 4: Master Side Code

#include "master.h"

#define MOTOR_PIN 3

void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// set the cursor to (column = 0,row = 0)
lcd.setCursor(0, 0);
lcd.print("Master Slave");
lcd.setCursor(0, 1);
// set the cursor to (column = 0,row = 1)
lcd.print("Demo");
// provide delay of 2 second
delay(300);
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB
}
pinMode(MOTOR_PIN, OUTPUT); // declare Motor pin to be an output
}

void loop() {
getTempearture();
getHumidity();
getSpeed();
setMotorSpeed();
lcdUpdate();
}

void setMotorSpeed() {
if (paramerters.Status[SPD_STA_INDX] == STATUS_OK) {
analogWrite(MOTOR_PIN, map(paramerters.Speed,0,100,0,255)); // set the Speed of Motor
}
}

// For temperture
// CMD : START_CHAR TEMP_CMD END_CHAR
// RESP: START_CHAR TEMP_RSP STATUS BYTE1 BYTE2 BYTE3 BYTE4 END_CHAR
void getTempearture() {
float temp;
float *ptemp = &temp;
unsigned char cmdBuffer[3] = { START_CHAR, TEMP_CMD, END_CHAR };
unsigned char rspBuffer[10] = { 0 };
unsigned char rspIndex = 0;
Serial.write(cmdBuffer, 3);
while (Serial.available() == 0)
;
do {
rspBuffer[rspIndex++] = Serial.read();
// this delay is provided because it takes approx 10 ms to receiev a new character at 9600 baurdrate
delay(10);
} while (Serial.available() != 0);
if ((rspBuffer[0] == START_CHAR) && (rspBuffer[1] == TEMP_RSP) && (rspBuffer[7] == END_CHAR)) {
if (rspBuffer[2] == STATUS_OK) {
ptemp = (float *)(rspBuffer + 3);
paramerters.Temperature = *ptemp;
}
paramerters.Status[TEM_STA_INDX] = rspBuffer[2];
}
}

// For humidity
// CMD : START_CHAR HUMIDITY_CMD END_CHAR
// RESP: START_CHAR HUMIDITY_RSP STATUS BYTE1 BYTE2 BYTE3 BYTE4 END_CHAR
void getHumidity() {
float hum;
float *phum = &hum;
unsigned char cmdBuffer[3] = { START_CHAR, HUMIDITY_CMD, END_CHAR };
unsigned char rspBuffer[10] = { 0 };
unsigned char rspIndex = 0;
Serial.write(cmdBuffer, 3);
while (Serial.available() == 0)
;
do {
rspBuffer[rspIndex++] = Serial.read();
// this delay is provided because it takes approx 10 ms to receiev a new character at 9600 baurdrate
delay(10);
} while (Serial.available() != 0);
if ((rspBuffer[0] == START_CHAR) && (rspBuffer[1] == HUMIDITY_RSP) && (rspBuffer[7] == END_CHAR)) {
if (rspBuffer[2] == STATUS_OK) {
phum = (float *)(rspBuffer + 3);
paramerters.Humidity = *phum;
}
paramerters.Status[HUM_STA_INDX] = rspBuffer[2];
}
}

// For speed
// CMD : START_CHAR SPEED_CMD END_CHAR
// RESP: START_CHAR SPEED_RSP STATUS BYTE1 END_CHAR
void getSpeed() {
int spd;
int *pspd = &spd;
unsigned char cmdBuffer[3] = { START_CHAR, SPEED_CMD, END_CHAR };
unsigned char rspBuffer[10] = { 0 };
unsigned char rspIndex = 0;
Serial.write(cmdBuffer, 3);
while (Serial.available() == 0)
;
do {
rspBuffer[rspIndex++] = Serial.read();
// this delay is provided because it takes approx 10 ms to receiev a new character at 9600 baurdrate
delay(10);
} while (Serial.available() != 0);
if ((rspBuffer[0] == START_CHAR) && (rspBuffer[1] == SPEED_RSP) && (rspBuffer[5] == END_CHAR)) {
if (rspBuffer[2] == STATUS_OK) {
pspd = (int *)(rspBuffer + 3);
paramerters.Speed = *pspd;
}
paramerters.Status[SPD_STA_INDX] = rspBuffer[2];
}
if (paramerters.Status[SPD_STA_INDX] == STATUS_OK) {
if (paramerters.Speed >= 50) {
//it indicates high speed
setLEDON();
} else {
//it indicates low speed
setLEDOff();
}
}
}

// For led ON
// CMD : START_CHAR LED_ON_CMD END_CHAR
// RESP: START_CHAR LED_ON_RSP STATUS END_CHAR
void setLEDON() {
unsigned char cmdBuffer[3] = { START_CHAR, LED_ON_CMD, END_CHAR };
unsigned char rspBuffer[10] = { 0 };
unsigned char rspIndex = 0;
Serial.write(cmdBuffer, 3);
while (Serial.available() == 0);
do {
rspBuffer[rspIndex++] = Serial.read();
// this delay is provided because it takes approx 10 ms to receiev a new character at 9600 baurdrate
delay(10);
} while (Serial.available() != 0);
if ((rspBuffer[0] == START_CHAR) && (rspBuffer[1] == LED_ON_RSP) && (rspBuffer[3] == END_CHAR)) {
if (rspBuffer[2] == STATUS_OK) {
paramerters.Led_state = 1;
}
paramerters.Status[LED_STA_INDX] = rspBuffer[2];
}
}

// For led OFF
// CMD : START_CHAR LED_OFF_CMD END_CHAR
// RESP: START_CHAR LED_OFF_RSP STATUS END_CHAR
void setLEDOff() {
unsigned char cmdBuffer[3] = { START_CHAR, LED_OFF_CMD, END_CHAR };
unsigned char rspBuffer[10] = { 0 };
unsigned char rspIndex = 0;
Serial.write(cmdBuffer, 3);
while (Serial.available() == 0)
;
do {
rspBuffer[rspIndex++] = Serial.read();
// this delay is provided because it takes approx 10 ms to receiev a new character at 9600 baurdrate
delay(10);
} while (Serial.available() != 0);
if ((rspBuffer[0] == START_CHAR) && (rspBuffer[1] == LED_OFF_RSP) && (rspBuffer[3] == END_CHAR)) {
if (rspBuffer[2] == STATUS_OK) {
paramerters.Led_state = 0;
}
paramerters.Status[LED_STA_INDX] = rspBuffer[2];
}
}

void lcdUpdate() {
lcd.clear();

// DisplaY Temperature
// set the cursor to (column = 0,row = 0)
lcd.setCursor(0, 0);
lcd.print("Tem=");
if (paramerters.Status[TEM_STA_INDX] == STATUS_OK) {
lcd.print(paramerters.Temperature);
} else {
lcd.print("NOK");
}

// DisplaY LED Status
lcd.print(" LED=");
if (paramerters.Status[LED_STA_INDX] == STATUS_OK) {
lcd.write(paramerters.Led_state | 0x30);
} else {
lcd.print("NOK");
}

// Display Humidity
// set the cursor to (column = 0,row = 1)
lcd.setCursor(0, 1);
lcd.print("Hum=");
if (paramerters.Status[HUM_STA_INDX] == STATUS_OK) {
lcd.print(paramerters.Humidity);
} else {
lcd.print("NOK");
}

// Display Speed
lcd.print(" SP=");
if (paramerters.Status[HUM_STA_INDX] == STATUS_OK) {
lcd.print(paramerters.Speed);
lcd.print('%');
} else {
lcd.print("NOK");
}
}


Master Code Explanation


The Master board controls a few functionalities based on data received from the Slave, and it displays information on an LCD screen. Let's break down the code step by step:

Library Inclusion: The code starts by including the necessary libraries, including "master.h", which likely contains definitions and declarations used in this code.

Constants: The code defines a constant MOTOR_PIN which represents the pin connected to a motor.

Setup Function:

Initializes the LCD display, printing a startup message.

Initializes serial communication and waits for the connection.

Sets the motor pin (MOTOR_PIN) as an output.

Loop Function:

Calls various functions to fetch temperature, humidity, speed, and updates the LCD accordingly.

getTemperature Function:

Sends a command to the Slave board to request temperature data.

Receives the response from the Slave and parses it.

Updates the paramerters structure with the received temperature data.

getHumidity Function:

Similar to getTemperature but for humidity data.

getSpeed Function:

Similar to the previous two functions but for speed data.

Additionally, it checks the received speed data and controls an LED based on it.

setLEDON Function:

Sends a command to the Slave to turn on an LED.

Receives the response and updates the LED state in the paramerters structure.

setLEDOff Function:

Similar to setLEDON but turns off the LED.

lcdUpdate Function:

Clears the LCD display.

Displays temperature, LED status, humidity, and speed on the LCD based on data received from the Slave.

Step 5: Slave Side Code

#include "slave.h"

#define ALARM_LED 8
#define LED_ON 0
#define LED_OFF 1

char serialInput;
int dataIndex = 0;
char databuffer[10] = { 0 };
bool dataRcvd = false; // whether the string receiving is completed.
unsigned char rspBuffer[10];

void setup() {
Serial.begin(9600); // initialize serial port
dht.begin(); // Initialize the DHT sensor
pinMode(ALARM_LED, OUTPUT); // Initialize alarm LED
digitalWrite(ALARM_LED, LED_OFF);
}

void loop() {
getTemphumidity();
getSpeed();
if (dataRcvd == true) {
// Check for start and end character
if ((databuffer[0] = START_CHAR) && (databuffer[2] = END_CHAR)) {
// Check if its the temperature command
// For temperture
// CMD : START_CHAR TEMP_CMD END_CHAR
// RESP: START_CHAR TEMP_RSP STATUS BYTE1 BYTE2 BYTE3 BYTE4 END_CHAR
if (databuffer[1] == TEMP_CMD) {
rspBuffer[0] = START_CHAR;
rspBuffer[1] = TEMP_RSP;
if (paramerters.Status[TEM_STA_INDX] == STATUS_OK) {
rspBuffer[2] = STATUS_OK;
memcpy((rspBuffer + 3), &(paramerters.Temperature), 4);
} else {
rspBuffer[2] = STATUS_NOT_OK;
memset((rspBuffer + 3), 0x00, 4);
}
rspBuffer[7] = END_CHAR;
Serial.write(rspBuffer, 8);
}
// Check if it is Humidity command
// For humidity
// CMD : START_CHAR HUMIDITY_CMD END_CHAR
// RESP: START_CHAR HUMIDITY_RSP STATUS BYTE1 BYTE2 BYTE3 BYTE4 END_CHAR
else if (databuffer[1] == HUMIDITY_CMD) {
rspBuffer[0] = START_CHAR;
rspBuffer[1] = HUMIDITY_RSP;
if (paramerters.Status[HUM_STA_INDX] == STATUS_OK) {
rspBuffer[2] = STATUS_OK;
memcpy((rspBuffer + 3), &(paramerters.Humidity), 4);
} else {
rspBuffer[2] = STATUS_NOT_OK;
memset((rspBuffer + 3), 0x00, 4);
}
rspBuffer[7] = END_CHAR;
Serial.write(rspBuffer, 8);
}
// Check if it is Speed command
// For speed
// CMD : START_CHAR SPEED_CMD END_CHAR
// RESP: START_CHAR SPEED_RSP STATUS BYTE1 END_CHAR
else if (databuffer[1] == SPEED_CMD) {
rspBuffer[0] = START_CHAR;
rspBuffer[1] = SPEED_RSP;
if (paramerters.Status[SPD_STA_INDX] == STATUS_OK) {
rspBuffer[2] = STATUS_OK;
memcpy((rspBuffer + 3), &(paramerters.Speed), 2);
} else {
rspBuffer[2] = STATUS_NOT_OK;
memset((rspBuffer + 3), 0x00, 2);
}
rspBuffer[5] = END_CHAR;
Serial.write(rspBuffer, 6);
}
// For led ON
// CMD : START_CHAR LED_ON_CMD END_CHAR
// RESP: START_CHAR LED_ON_RSP STATUS END_CHAR
else if (databuffer[1] == LED_ON_CMD) {
rspBuffer[0] = START_CHAR;
rspBuffer[1] = LED_ON_RSP;
digitalWrite(ALARM_LED, LED_ON);
rspBuffer[2] = STATUS_OK;
rspBuffer[3] = END_CHAR;
Serial.write(rspBuffer, 4);
}
// For led OFF
// CMD : START_CHAR LED_OFF_CMD END_CHAR
// RESP: START_CHAR LED_OFF_RSP STATUS END_CHAR
else if (databuffer[1] == LED_OFF_CMD) {
rspBuffer[0] = START_CHAR;
rspBuffer[1] = LED_OFF_RSP;
digitalWrite(ALARM_LED, LED_OFF);
rspBuffer[2] = STATUS_OK;
rspBuffer[3] = END_CHAR;
Serial.write(rspBuffer, 4);
}
}
dataRcvd = false;
memset(databuffer, 0x00, sizeof(databuffer));
}
}

void getTemphumidity() {
static unsigned long int previousTick = 0;
static unsigned long int currentTick;

float temperature;
float humidity;

currentTick = millis();
if ((currentTick - previousTick) >= 100) {
previousTick = currentTick;
// Read temperature and humidity from the DHT sensor
temperature = dht.readTemperature(false);
humidity = dht.readHumidity();

// Check if the sensor reading is valid (non-NaN)
if (!isnan(temperature)) {
paramerters.Temperature = temperature;
paramerters.Status[TEM_STA_INDX] = STATUS_OK;
// Serial.print("Temperature: ");
// Serial.print(temperature);
} else {
paramerters.Status[TEM_STA_INDX] = STATUS_NOT_OK;
}

if (!isnan(humidity)) {
paramerters.Humidity = humidity;
paramerters.Status[HUM_STA_INDX] = STATUS_OK;
// Serial.print(" °F, Humidity: ");
// Serial.print(humidity);
// Serial.println("%");
} else {
paramerters.Status[HUM_STA_INDX] = STATUS_NOT_OK;
}
}
}

void getSpeed() {
int analogPin = A0; // potentiometer wiper (middle terminal) is connected to analog pin 0
int analogVal = 0; // variable to store the value read
analogVal = analogRead(analogPin); // read the Analog input pin
paramerters.Speed = map(analogVal, 0, 1023, 0, 100);
paramerters.Status[SPD_STA_INDX] = STATUS_OK;
}
/*
SerialEvent occurs whenever a new data comes in the hardware serial RX. This
routine is run between each time loop() runs, so using delay inside loop can
delay response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
// get the new byte:
serialInput = Serial.read();
databuffer[dataIndex++] = serialInput;
// if the incoming character is a line feed character '\n', set a flag so the main loop can do something about it
if (serialInput == END_CHAR) {
dataRcvd = true;
dataIndex = 0;
}
}
}


Slave Code Explanation


The Slave board collects data from sensors (temperature and humidity) and a potentiometer, and it can also control an LED. Let's go through the code step by step:

Library Inclusion: The code includes necessary libraries and defines constants such as ALARM_LED, LED_ON, and LED_OFF.

Global Variables: Variables like serialInput, dataIndex, databuffer, and dataRcvd are declared. These are used for receiving and processing data from the Master board.

Setup Function:

Initializes serial communication and the DHT sensor.

Sets the pin connected to the alarm LED (ALARM_LED) as an output and turns it off.

Loop Function:

Calls functions to fetch temperature/humidity data and potentiometer readings.

Checks if data has been received from the Master board and processes it accordingly.

getTemperatureHumidity Function:

Reads temperature and humidity from the DHT sensor at intervals.

Updates the parameters structure (paramerters) with the sensor data and status indicators.

getSpeed Function:

Reads the analog value from a potentiometer connected to pin A0.

Maps the analog value to a speed percentage and updates the parameters structure.

serialEvent Function:

This is an interrupt service routine that gets called whenever new data is available on the serial port.

It reads the incoming data byte by byte, storing it in databuffer.

When the end character (END_CHAR) is received, it sets the dataRcvd flag to true, indicating that a complete command has been received.

Command Handling:

Inside the loop, when dataRcvd is true, the code checks for start and end characters in the received command.

Depending on the command type (temperature, humidity, speed, LED on, LED off), appropriate responses are prepared and sent back to the Master board.


Code Credit: Play with Circuit

Step 6: Output