Arduino passing temperature readings to a neighbor Arduino: three ways of serial communication

by Floris Wouterlood – The Netherlands – July 17, 2017

Summary
Sometimes in a project the number of available pins on an Arduino limits the ambitions. This may happen for instance when a number of sensors are planned whose data need to be displayed on a pin-hungry screen. Under these conditions it may be handy to assign to one Arduino the job of monitoring the sensors while the job of displaying the results is assigned to another Arduino. Data from one Arduino can be transmitted to a neighbour via serial communication. In this paper we play with three types of serial communication: standard TX-RX, Soft Serial and I2C.
Serial communication requires two pins of the microcontroller board. For proper functioning the GND pins of both Arduinos need to be connected.
This example includes temperature sensing with a Dallas DS18B20 probe on an Arduino (Nano #1) while Arduino #2 (Nano #2) is equipped with a 128×32 monochrome OLED display that shows the readings.

Introduction
Conditions may arise where the number of available pins on an Arduino constrain a project. One example is the construction of a weather station that includes barometer, temperature and humidity probes while one wants to show the results simultaneously on a TFT display. Of course one can use an Arduino Mega that has lots more pins available than a humble Arduino Uno or Nano, but the challenge is to squeeze as much as possible out of a standard Arduino. Fortunately every Arduino has serial communication pins that can be used to exchange data with another device, for instance a second Arduino. We can exploit this functionality to divide jobs over two Arduinos: number one measures and number two displays.
Arduinos can communicate with each other and the outer world via an astounding spectrum of possibilities: serial communication, Bluetooth, networking, and even infrared. We deal in this paper with serial communication. Three different kinds of serial communication exist for the Arduino: the standard way that uses the TX and RX pins, Soft Serial that allows programmers to select free pins to do the communication job, and I2C communication that uses pins A4 and A5. Soft serial communication is extremely handy when pins are occupied by devices that need specific ‘hard’ pins in order to work.

In this example two Arduino Nanos are connected. Nano #1 reads temperature data from a Dallas DS18B20 temperature probe, then communicates the data to Nano #2 which then displays the temperature value on a 128×32 monochrome OLED display.

Electronics and supplies
2x Arduino Nano microcontroller board, 2x breadboard, 1x Dallas DS18B20 temperature sensor, 2x led, 2x 220O, 3x 4.7 kO resistor, jumper wires, 128×24 I2C OLED display, interruptor switch.

Nano #1: Connecting a Dallas DS18B20 temperature probe
Ready to go (wired) Dallas DS18B20 temperature probes have three wires: red, black and white (sometimes yellow). Red is 5V, black is GND and the white or yellow wire is the data wire. In this example, the data wire is connected to pin 8 of Nano #1. A 4.7 O resistor functions a a pullup between the sensor’s data pin and 5V (figure 1).
Device address: Each DS18B20 that leaves the factory has a specific device address. The sensor used in this paper has deviec address 0x28, 0xFF, 0xD7, 0x91, 0x92, 0x16, 0x04, 0x69. You can identify the device address of your DS18B20 with the sketch’ DS18B20_address_finder’.

Nano #2: Wiring the I2C OLED display
The 128×32 monochrome OLED display is connected to Nano #2 in the standard way: VCC to 5V, GND to GND, SCL to pin A5 and SDA to pin A4 of the Nano.

Extra
Each Nano has a led at pin 6 that functions as a ‘cycle indicator’ during communication.

1. Standard Serial communication

Figure 1 shows the wiring. The TX pin of Nano #1 is connected to pin RX of Nano #2, and pin RX of Nano #1 is connected to the TX pin of Nano #2. GND of both Nano’s is interconnected.
The interruptor switch is necessary in the communication line to uncouple serial communication between the Arduinos during uploading of a sketch via the usb port to one of the Nanos. The alternative is to disconnect the Nanos during an upload activity.

Figure 1: Wiring for Standard Serial communication. Nanos #1 and #2. Nano #1 receives via its pin 8 temperature data from a DS18B20 sensor and transmits the measured data via serial commuinication wusing pins TX and RX to Nano #2. The latter displays the measured temperature on a I2C device, a 128×32 OLED display.

Standard Serial sketches


Nano #1: The Dallas DS18B20 temperature probe reads temperatures in two decimals where the second decimal is irrelevant because it is below the probe’s resolution. Nevertheless the type of variable necessesary to work with decimal readings of anything such as temperatures is a ‘float’. Serially transmitting floats is quite complex. That is why the sketches contain a trick: in Nano #1 the float is multiplied by 10 to convert the value to an integer. The integer is transmitted to Nano #2 and here the value is divided by 10 and converted to a float. Below is the complete ‘bare’ sketch. An up- and running Arduino IDE on your computer is necessary. Copy-paste the instructions below into an empty new file in the Arduino IDE.

// Nano_1_Serial_Comm_DS18B20
//
// simple TX-RX serial connectivity
// Nano #1 reads temperature from a DS18B20 as a float
// float is then converted to integer (= float*10)
// Nano #1 sends integer values via TX
// Nano #2 receives and displays on OLED 128×32
// don’t forget to interconnect GND of the two boards
// Serial monitor output of Nano #1 minimalized
// July 14, 2017 Floris Wouterlood
// public domain

#include <OneWire.h>
#include <DallasTemperature.h>

#define ledpin 6
#define ONE_WIRE_BUS_PIN 8

OneWire oneWire (ONE_WIRE_BUS_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress Probe_01 = { 0x28, 0xFF, 0xD7, 0x91, 0x92, 0x16, 0x04, 0x69};
float DS18B20_01 = 0;
int DS18B20_int = 0;

void setup()
{
Serial.begin (9600);
pinMode (ledpin, OUTPUT);
Serial.print (“Dallas Control Library Version “);
Serial.println (DALLASTEMPLIBVERSION);

// Initialize the Temperature measurement library
sensors.begin();

// set the resolution to 10 bit (can be 9 to 12 bits .. lower is faster)
sensors.setResolution (Probe_01, 10);
Serial.println ();
Serial.print (“Devices on bus = “);
Serial.println (sensors.getDeviceCount () );
Serial.println (“Getting temperatures… “);
Serial.println (“================================ “);

}

void loop(){

// read DS18B20 probe and convert to int
delay(100);
sensors.requestTemperatures (); // read temperature on one wire bus device
printTemperature (Probe_01);
DS18B20_int = (10*(DS18B20_01)); // turn float into int by multiplication
digitalWrite (ledpin, HIGH); // turn the LED on

// send to serial port
Serial.println (DS18B20_int); // send to RX on Nano #2
delay(100);
fire_led();

}

// subroutine fire led ===================================
void fire_led () {

digitalWrite (ledpin, HIGH); // LED on
delay (400);
digitalWrite (ledpin, LOW); // LED off

}

// subroutine printTemperature ===========================

void printTemperature (DeviceAddress deviceAddress)
{

float tempC = sensors.getTempC (deviceAddress);

if (tempC == -127.00)
{
Serial.print (“Error getting temperature “);
}
else
{
DS18B20_01=(tempC); // save temp reading into float viariable
}
}
// end of sketch Nano #1 Standard Serial comm


Nano #2

The microcontroller receives data on its RX pin, converts to a float variable named ‘DS18B20_01’ and displays the value of that float on the OLED via I2C.

// Nano_2_Serial_Comm_OLED
//
// simple TX-RX serial connectivity
// Nano #1 reads temperature from a DS18B20 as a float
// that float is converted to integer (= float*10)
// Nano #1 sends integers
// Nano #2 receives and displays on OLED 128×32
// don’t forget to interconnect GND of the two boards
// Serial monitor output as well
// July 14, 2017 Floris Wouterlood
// public domain

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_SSD_1306.h>
#include <Adafruit.GFX.h>

#define OLED_RESET 4
#define ledpin 6
Adafruit_SSD1306 display (OLED_RESET);
int DS18B20_int;
float DS18B20_01;

void setup()
{
Serial.begin (9600);
Serial.println (“Serial Monitor Connected”);
pinMode (ledpin, OUTPUT);

// OLED setup part ===================================
Serial.begin (9600);
Serial.println (“Starting …”);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay ();
display.setTextSize (4);
display.setTextColor (WHITE);
display.setCursor (0,0);
display.print (“OLED start……….”);
Serial.println (“OLED started …”);
display.display ();
delay (500);
display.clearDisplay ();
display.setCursor (0,0);
display.setTextSize (1);
display.print (“temp: “);
display.display ();
display.setTextSize (3);
}

void loop()
{
int incoming = Serial.available();

if (incoming > 0)
{

DS18B20_int = Serial.parseInt(); // reads integers as integer rather than ASCI
// anything else returns 0
DS18B20_01 = (DS18B20_int/10.0);
Serial.print (“Temp: “);
Serial.print (DS18B20_01,1);
Serial.println (” *C”);
oled_output ();
fire_led ();
}
}

// subroutines ===============================

// subroutine output 128 x 32 OLED

void oled_output (){

display.clearDisplay ();
display.setCursor (0,0);
display.setTextSize (1);
display.print (“temp: “);
display.setTextSize (3);
display.setCursor (40,0);
display.print (DS18B20_01,1);
display.display ();
delay (250);
}

// subroutine fire led ===========================

void fire_led () {

digitalWrite(ledpin, HIGH); // LED on
delay(500);
digitalWrite(ledpin, LOW); // LED off
}

// end of sketch Nano #2 Standard Serial comm

2. Soft Serial communication

Figure 2 shows the wiring for this configuration. As you can see, there is not much difference between Soft Serial and Standard Serial. Note that only on Nano #1 the wires that in Standard Serial communication are connected to TX and RX now have been connected to pins 3 and 4. On Nano #2 nothing is changed. Thus, on Nano #1 pin 3 acts as ‘soft’ TX and pin 4 acts as ‘soft’ RX. Everything else in the wiring is unchanged. What is needed for compilation is the library SoftwareSerial.h.

Figure 2: Wiring for Soft Serial communication. Nano #1 receives via its pin 8 temperature data from a DS18B20 sensor and transmits the measured data via Soft Serial communication to Nano #2. The latter displays the measured temperature on a I2C device, a 128×32 OLED display.

Soft Serial sketch

Only for Nano #1 a sketch is presented here since the sketch for Nano #2 is identical to the standard serial sketch, above.

Nano #1: Here the same events take place as in the Standard Serial Sketch. The Dallas DS18B20 temperature probe reads temperatures in two decimals where the second decimal is irrelevant because it is below the probe’s resolution. Nevertheless a ‘float’ variable is necessary to hold data from this probe. Also in this sketch the “float to integer” conversion trick is used. The integer is transmitted to Nano #2, just as in Standard Serial communication. Below is the complete ‘bare’ sketch for Nano #1. An up- and running Arduino IDE on your computer is necessary. Copy-paste the instructions below into an empty new file in the Arduino IDE.


// Nano_1_Soft_Serial_Comm_DS18B20
//
// simple TX-RX Soft Serial connectivity
// Nano #1 reads temperature from a DS18B20 as a float
// float is then converted to integer (= float*10)
// Nano #1 sends integer values via Soft Serial – here TX pin 3 – RX pin 4
// Nano #2 sends-receives standard TX-RX
// Nano #2 displays on OLED 128×32
// don’t forget to interconnect GND of the two boards
// Serial monitor output of Nano #1 minimalized
// July 14, 2017 Floris Wouterlood
// public domain

// soft serial after example by Tom Igoe

#include
#include
#include // soft serial
#define rxPin 4 // soft serial
#define txPin 3 // soft serial
#define ledPin 6
#define ONE_WIRE_BUS_PIN 8

// DS18B20 sensor section
OneWire oneWire(ONE_WIRE_BUS_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress Probe_01 = { 0x28, 0xFF, 0xD7, 0x91, 0x92, 0x16, 0x04, 0x69};
float DS18B20_01 = 0;
int DS18B20_int = 0;

// set up a new (soft) serial port
SoftwareSerial mySerial = SoftwareSerial (rxPin, txPin);

void setup()
{

// define pin modes for tx, rx, led pins:
pinMode (rxPin, INPUT);
pinMode (txPin, OUTPUT);
pinMode (ledPin, OUTPUT);

// set the data rate for the SoftwareSerial port
mySerial.begin(9600);
mySerial.println(“Hello World – SoftwareSerial”);

// run Serial Monitor as well
Serial.begin (9600);
pinMode (ledPin, OUTPUT);

Serial.print (“Dallas Control Library Version “);
Serial.println (DALLASTEMPLIBVERSION);

// Initialize the Temperature measurement library
sensors.begin();

// set the resolution to 10 bit (can be 9 to 12 bits .. lower is faster)
sensors.setResolution (Probe_01, 10);
Serial.println ();
Serial.print (“Devices on bus = “);
Serial.println (sensors.getDeviceCount());
Serial.println (“Getting temperature… “);
Serial.println (“================================ “);
}

void loop (){

// read DS18B20 probe and convert to int
delay(100);
sensors.requestTemperatures(); // read temperature on one wire bus device
printTemperature(Probe_01);
DS18B20_int = (10*(DS18B20_01)); // turn float into int through multiplying with 10
digitalWrite(ledPin, HIGH); // turn the LED on

// send to serial port
mySerial.println (DS18B20_int); //Send i to Rx Arduino
Serial.println (DS18B20_01); //Send i to Rx Arduino
delay(100);
fire_led();
}

// subroutine fire led ===================================
void fire_led(){

digitalWrite (ledPin, HIGH); // LED on
delay (400);
digitalWrite (ledPin, LOW); // LED off
}

// subroutine printTemperature ===========================
void printTemperature(DeviceAddress deviceAddress)
{

float tempC = sensors.getTempC(deviceAddress);

if (tempC == -127.00)
{
Serial.print(“Error getting temperature “);
}
else
{
DS18B20_01=(tempC); // this is to enable future expandion to multiple probes
}
}

// end of sketch Nano #1 Soft Serial comm

3. I2C serial communication

Nano #2 already uses I2C communication to send the received data to the OLED display. Because I2C is a ‘bus’ type of communication, with each device on the bus having an unique address, the exchange of information to and from the Arduino and peripheral devices, be it an OLED or a ‘slave’ Arduino, is to devices with a specific address. I2C and one-wire buses work independently from each other.
Figure 3 shows the wiring of the two Nanos in the I2C configuration. Pins A4 and A5 of Nano #1 are connected to pins A4 and A5 of Nano #2. Pins A4 and A5 of Nano#2 are connected to pins SDA and SCK on the OLED display. An interruyptor switch in the communication wires is not necessary. Experts in I2C communication emphasize to have 4.7kO pullup resistors on the A4 and A5 lines, especially if the master and the slave are far located apart (meters). With short connections the pullups are not important. The data pin of the Dallas DS18B20 probe is connected to pin 8, just as in the Standard Serial and Soft Serial configurations.

Figure 3: Wiring for I2C serial communication. Nano #1 (the I2C slave) receives via its pin 8 temperature data from a DS18B20 sensor and transmits on request from the I2C master (Nano #2) the measured data to the I2C master. Nano #2 displays the measured temperature on a I2C device (128×32) OLED display.

In contrast to the ‘democratic’ Standard Serial communication where the partners are equal, I2C communication between Arduinos works with ‘masters’ and ‘slaves’. There can be only one master who can govern a maximum of 32 slaves. In my configuration, Nano #1 with the temperature sensor is the slave and Nano #2 the master. The I2C OLED display is alo a kind of slave device to the master Nano.

I2C Sketches

Because of the master-slave configuration writing or reading requests from the master to a particular slave need to be addressed to that specific slave. The slave identity of Nano #1 in this example is ‘8’. The master doesn’t need an identity because there is only one master (maybe the boss is number ‘1’). Another issue that differs froms Standard Serial data traffic is the concept of communication. I2C can transmit only numbers between 0 and 255, that is I2C typically transmits ASCII characters. A trick to send anyting from one Arduino to another one with the I2C protocol is to ‘disguise’ numbers as ASCII characters. This process is called ‘coding’. Thus, the slave reads the temperature from the sensor, codes the value as a string of characters and waits patiently until the master requests data. The master receives the string of characters, decodes the string into a value and sends this value to the display.
So far so good. Even ‘floats’ such as presented by a Dalls DS18B20 sensor can be coded and decoded in I2C communication. In the present setup we communicate floats. The instruction in the slave responsible for coding is called ‘dtostrf’ and the instruction in the master responsible for decoding is named ‘atof’.

Nano #1 (slave): The Dallas DS18B20 temperature probe attached to Nano #1 reads temperatures in two decimals (to be stored in a variable that needs to be declared as a ‘float’) where the second decimal is actully irrelevant because it is below the probe’s resolution. It is possible to convert the float value into an integer value, but because we have to code to a series of characters (‘string’) anyway we can code the float directly into a character string, which saves memory allocations. Some experimentation gave as result that sending the entire float from Nano #1 to Nano #2 and then removing the second decimal is more accurate than removing the second decimal before the float is coded. As a result in the Nano #1 sketch a string of 5 characters is made up and sent to Nano #2, that is two characters, one containing the 10s of degrees and the next the unit degrees, then a period (representing the decimal point) and next two characters representing the decimals. Below are the complete ‘bare’ sketches for Nano #1. An up- and running Arduino IDE on your computer is necessary. Copy-paste the instructions below into an empty new file in the Arduino IDE.


// Nano_1_I2C_comm_slave_DS18B20
// this Nano is the i2c slave
// slave operates a DS18B20 temperature sensor
//
// output of DS18B20 are floats
// master requests temperatures
// slave sends temp float with two decimals coded as chars
// master decodes chars to float
// and displays with one decimal on 128×32 OLED
// 16 July 2017
// Floris Wouterlood
// public domain

#include
#include
#include
#define ledpin 6
#define ONE_WIRE_BUS_PIN 8

OneWire oneWire(ONE_WIRE_BUS_PIN);
DallasTemperature sensors(&oneWire);
DeviceAddress Probe_01 = { 0x28, 0xFF, 0xD7, 0x91, 0x92, 0x16, 0x04, 0x69};
float DS18B20_float = 0;
char t[10]; //empty array where to put the numbers going to the master
volatile int Val; // variable used by the master to sent data to the slave

void setup() {

Serial.begin(9600); // start serial monitor
pinMode (ledpin, OUTPUT);
Wire.begin(8); // between brackets slave id: #8
Wire.onRequest(requestEvent); // master will requests data
Wire.onReceive(receiveEvent); // what to do when receiving data

Serial.print (“Dallas Control Library Version “);
Serial.println (DALLASTEMPLIBVERSION);

sensors.begin(); // initialize temperature probe
sensors.setResolution(Probe_01, 10); // resolution 10 is sufficient
Serial.println();
Serial.print(“Devices on one wire bus = “);
Serial.println (sensors.getDeviceCount());
Serial.println (“Getting temperatures… “);
Serial.println (“================================ “);
}

void loop() {

// read DS18B20 probe a

sensors.requestTemperatures (); // read temperature on one wire bus device
printTemperature (Probe_01);
digitalWrite (ledpin, HIGH); // turn the LED on
fire_led();
Serial.print (“temp = “);
Serial.print (DS18B20_float,2);
Serial.println (” *C”);

// convert float to string with one decimal (floatVar, minStringWidthIncDecimalPoint,
// numVarsAfterDecimal, empty array)

dtostrf(DS18B20_float, 3, 2, t);
delay(500);
}

// subroutines ============================================

// subroutine: what to do when asked for data =============
void requestEvent() {
Wire.write(t);
}

// what to do when receiving data from master
void receiveEvent(int howMany)
{Val = Wire.read();}

// subroutine fire led ===================================
void fire_led () {

digitalWrite (ledpin, HIGH); // LED on
delay (400);
digitalWrite (ledpin, LOW); // LED off
}

// subroutine printTemperature ===========================

void printTemperature (DeviceAddress deviceAddress)
{
float tempC = sensors.getTempC (deviceAddress);

if (tempC == -127.00)
{
Serial.print (“Error getting temperature “);
}
else
{
DS18B20_float=(tempC); // float variable
}
}

// end of sketch Nano_1_1 I2C_comm_slave_DS18B20

 

Nano #2 (master): This Nano requests a character string from the slave that contain the coded temperaturte information, converts the character strings to a float and controls the OLED LCD display that displays the temperature with one decimal.
Below is the ‘bare’ sketch for Nano #12. Copy-paste the instructions below into an empty new file in the Arduino IDE.


// Nano_2_I2C_comm_master_OLED
// this Nano is the i2c master
// it governs the i2c OLED 128×32 display
// requests the slave temperatures
// slave sends temp floatwith two decimals coded as chars
// master decodes chars to float with atof function
// and displays with one decimal on 128×32 OLED
// 16 July 2017
// Floris Wouterlood
// public domain

#include
#include
#include
#include

#define ledpin 6
#define OLED_RESET 4
Adafruit_SSD1306 display (OLED_RESET);

float DS18B20_float = 0.0;
float DS18B20_01 = 0;
char t[10] = {}; // empty array for parking bytes received from slave
volatile int Val; // variable used by master to send data to slave

void setup() {

Wire.begin (); // join i2c bus
Serial.begin (9600); // start serial monitor
pinMode(ledpin, OUTPUT);

// OLED setup part =====================================
Serial.begin (9600);
Serial.println (“I2C master starting …”);
display.begin (SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay ();
display.setTextSize (4);
display.setTextColor (WHITE);
display.setCursor (0,0);
display.print (“OLED start……….”);
Serial.println (“OLED started …”);
display.display ();
delay (500);
display.clearDisplay ();
display.setCursor (0,0);
display.setTextSize (1);
display.print (“temp: “);
display.display ();
display.setTextSize (3);
// end OLED setup part ==================================

}

void loop() {

// get data from slave ==================================
Wire.requestFrom(8, 5); // request 5 bytes from slave device #8
int i = 0; // counter for each byte as it arrives

while (Wire.available()) {
t[i] = Wire.read(); // arriving characters parked in array t
i = i + 1;

}

// char array to float section ==========================
DS18B20_float = atof (t);
Serial.print (“temp = “);
Serial.print (DS18B20_float,2); // serial monityor print with 2 decimals
Serial.println (” *C”);
delay(500); // time to relax

// send data to slave. here I am just sending ‘2’ ======
Val = 2;
Wire.beginTransmission (8);
Wire.write (Val);
Wire.endTransmission ();
oled_output (); // refresh OLED
fire_led ();
}

// subroutines ==========================================

// subroutine output 128 x 32 OLED

void oled_output (){

display.clearDisplay ();
display.setCursor (0,0);
display.setTextSize (1);
display.print (“temp: “);
display.setTextSize (3);
display.setCursor (40,0);
display.print (DS18B20_float,1); // one decimal OLED output
display.display ();
delay (250);
}

// subroutine fire led ==================================

void fire_led () {

digitalWrite (ledpin, HIGH); // LED on
delay (500);
digitalWrite (ledpin, LOW); // LED off
}

// end of sketch Nano_2_1 I2C_comm_master_OLED

 

Discussion

Serial Communication can be very handy to divide jobs over two Arduinos where otherwise multiple jobs performed on one Arduino may overwhelm the microcontroller, overload memory or simply need more pins than available. Standard Serial, as it name implies, is the classical communication protocol with many examples available on the Internet. Soft Serial offers flexibility in the choice of pins. Serial communication In principle is between two Arduinos. I2C is especially handy when more than two Arduinos are in the configuration.

Easy communication comes at a price. The Standard Serial sketches amount on Nano #1 and Nano#2 in compiled form 7,430 and 12,924 bytes, respectively. These numbers are for Soft serial communication 9,550 and 12,924 bytes while I2C uses the most memory: 10,810 bytes for the slave Nano #1 and 13,702 for the master Nano #2.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s