Hallberg Gary - Arduino Special Functions (Arduino Short Reads. Book 6) - 2021
Hallberg Gary - Arduino Special Functions (Arduino Short Reads. Book 6) - 2021
Gary Hallberg
The idea underpinning the Arduino short reads series is to provide a comprehensive, easy to follow
tutorial set and reference guide for anybody wanting to learn about Arduino and basic electronics.
Having a short reads series means that students and hobbyists can select key topics of interest in the
field with minimal outlay. The series aims to provide an easy introduction to all topics of interest and
expand on these topics to provide a broader and deeper understanding of that focus area. The books
are currently only available in Kindle format to provide a very inexpensive package backed up by video
content and interactive social media.
The series is aimed at youngsters and adults interested in getting into electronics and it takes a modern
approach, combining the use of the inexpensive software driven Arduino controller board, with a
multitude of sensors and discreet electronic components. The experiments in this series of books are
easy to follow, inexpensive to implement and compelling for all interested in STEM education. I hope
to inspire anybody looking for a future career in technology or to simply to have fun.
The first book of this series looks at the Arduino microcontroller and explains its operation and purpose.
Experiments look to show you how to set up the programming environment and drive LEDs as well as
read input from switches, thermistors and photocells. Book 1 will give you a solid foundation of how
some basic electronic components work and how to use them to make simple designs.
Books 7 and 8 in this Arduino Short Reads Series are still being written but the series focuses on the
following:
• Book 1 – First Steps with Arduino (published)
• Book 2 – Working with Displays (published)
• Book 3 – Controlling Motors (published)
• Book 4 – Range Finding, Object Detection and Object Avoidance (published)
• Book 5 – Building a Simple Rover (published)
• Book 7 – Communications with Arduino and the ESP32
• Book 8 – The Arduino Leonardo
If you find this series of books useful then please leave your review and rating on
Amazon.
Follow North Border Tech Training on Facebook and Twitter for the latest news and
insights as to how this series will develop.
Foreword
Book 1 of this series sets out to provide a basic understanding of the Arduino platform, how to program
it and how to interface simple electronics. This book builds on that basic knowledge and rounds out the
capabilities of the platform by looking at the special functions that the Arduino Uno supports. This book
is a great complimentary text for Book 1.
You may have noticed that some of the I/O pins of the Arduino Uno have a second function. It is these
we will explore. They include serial data transmission, the SPI bus and the I2C interface. We will also
look at hardware and timed interrupts. Mastering these skills will open a world of much more
sophisticated sensors and devices to enhance your current and future project work.
I have no doubt the skills learnt here will allow you to develop more engaging and useful projects.
Without further delay let us get into the content.
Prerequisites for this Book
This book assumes you have read Book 1 of the series (‘First Steps with Arduino’) or you already have
some experience of the Arduino platform, how to set up the IDE and undertake some basic Arduino
programming tasks. Basic functions such as ‘digitalRead’ and ‘digitalWrite’ are not covered here
but are explained in Book 1.
Download the Code
You can download the Arduino Code for the experiments in this book from GitHub using the link below.
https://github.com/ghallberg-nbtt/sturdy-octo-goggles
I recommend that you do this as I have removed some of the comments from the code in the book
listings to aid readability in print form. I strongly recommend that you comment your code heavily to
facilitate understanding and to become familiar with best engineering practice.
Chapter 1: Serial Data Communications
In this chapter we will explore serial communications to and from the Arduino Uno. In Book 5 we used
a Bluetooth module to allow remote control of the autonomous vehicle. We will use the same module
here, but first we will take a close look at how serial communications work and explore in more detail
the Serial Monitor using the USB port.
Serial Communications
Serial communications form part of the earliest form of electronic communications. The Telegraph is
one such example of a system that uses serial communications. They are still important in today’s
electronic systems. You will most likely encounter this form of communication with the Universal Serial
Bus or USB that is pretty much ubiquitous in modern electronic equipment including the Arduino
range.
Efficient serial communications buses require a transmit path, a receive path and a voltage reference to
act as ground. These systems are referred to as 3-wire or full duplex, as data can be transferred in the
transmit and receive directions at the same time. Such a system is depicted in Figure 1-1. There are also
systems that share a single transmit and receive path. These 2-wire systems are also referred to as half
duplex as it is not possible to transmit and receive data at the same time.
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); //Ensure LED is off
Serial.begin (38400); //initiate serial monitor at 38400bps
}
void loop() {
// put your main code here, to run repeatedly:
if (Serial.available() > 0) {
byte incomingByte = Serial.read();
//1 = on and 0 = off
if (incomingByte == 49) {
digitalWrite(LED_BUILTIN, HIGH); //Turn on LED
Serial.println ("LED on");
} else if (incomingByte == 48) {
digitalWrite(LED_BUILTIN, LOW);
Serial.println ("LED off");
} else {
Serial.println ("invalid input");
}
}
}
When data is available to read, we initiate a byte variable to hold that data. The ‘Serial.read()’
function actually reads the data a byte at a time and returns a byte and not a ‘char’ variable. The
program works by turning the LED on when the character ‘1’ is pressed and off when ‘0’ is pressed. All
other input is ignored. The Sketch also writes to the Serial Monitor window to confirm the state of the
LED.
So why do we look for ‘49’ to represent the character ‘1’ and ‘48’ for ‘0’? The answer lies in the fact that
‘Serial.read()’ function returns a byte and only reads one character at a time. For serial
communications, the characters are encoded as series of 8-bit binary numbers. These can be
represented in binary, decimal and hexadecimal format. This code is standard as is called the ASCII
(American Standard Code for Information Interchange) code.
Table 1-2 shows the ACSII code, and you can see that the character ‘1’ is represented by the number ‘49’
and ‘0’ by ‘48’.
CHAR DEC HEX CHAR DEC HEX CHAR DEC HEX CHAR DEC HEX CHAR DEC HEX CHAR DEC HEX CHAR DEC HEX CHAR DEC HEX
[NUL] 0 00 32 20 @ 64 40 ` 96 60 € 128 80 160 A0 À 192 C0 à 224 E0
[SOH] 1 01 ! 33 21 A 65 41 a 97 61 (n/a) 129 81 ¡ 161 A1 Á 193 C1 á 225 E1
[STX] 2 02 " 34 22 B 66 42 b 98 62 ‚ 130 82 ¢ 162 A2 Â 194 C2 â 226 E2
[ETX] 3 03 # 35 23 C 67 43 c 99 63 ƒ 131 83 £ 163 A3 Ã 195 C3 ã 227 E3
[EOT] 4 04 $ 36 24 D 68 44 d 100 64 „ 132 84 ¤ 164 A4 Ä 196 C4 ä 228 E4
[ENQ] 5 05 % 37 25 E 69 45 e 101 65 … 133 85 ¥ 165 A5 Å 197 C5 å 229 E5
[ACK] 6 06 & 38 26 F 70 46 f 102 66 † 134 86 ¦ 166 A6 Æ 198 C6 æ 230 E6
[BEL] 7 07 ' 39 27 G 71 47 g 103 67 ‡ 135 87 § 167 A7 Ç 199 C7 ç 231 E7
[BS] 8 08 ( 40 28 H 72 48 h 104 68 ˆ 136 88 ¨ 168 A8 È 200 C8 è 232 E8
[HT] 9 09 ) 41 29 I 73 49 i 105 69 ‰ 137 89 © 169 A9 É 201 C9 é 233 E9
[LF] 10 0A * 42 2A J 74 4A j 106 6A Š 138 8A ª 170 AA Ê 202 CA ê 234 EA
[VT] 11 0B + 43 2B K 75 4B k 107 6B ‹ 139 8B « 171 AB Ë 203 CB ë 235 EB
[FF] 12 0C , 44 2C L 76 4C l 108 6C Œ 140 8C ¬ 172 AC Ì 204 CC ì 236 EC
[CR] 13 0D - 45 2D M 77 4D m 109 6D (n/a) 141 8D 173 AD Í 205 CD í 237 ED
[SO] 14 0E . 46 2E N 78 4E n 110 6E Ž 142 8E ® 174 AE Î 206 CE î 238 EE
[SI] 15 0F / 47 2F O 79 4F o 111 6F (n/a) 143 8F ¯ 175 AF Ï 207 CF ï 239 EF
[DLE] 16 10 0 48 30 P 80 50 p 112 70 (n/a) 144 90 ° 176 B0 Ð 208 D0 ð 240 F0
[DC1] 17 11 1 49 31 Q 81 51 q 113 71 ‘ 145 91 ± 177 B1 Ñ 209 D1 ñ 241 F1
[DC2] 18 12 2 50 32 R 82 52 r 114 72 ’ 146 92 ² 178 B2 Ò 210 D2 ò 242 F2
[DC3] 19 13 3 51 33 S 83 53 s 115 73 “ 147 93 ³ 179 B3 Ó 211 D3 ó 243 F3
[DC4] 20 14 4 52 34 T 84 54 t 116 74 ” 148 94 ´ 180 B4 Ô 212 D4 ô 244 F4
[NAK] 21 15 5 53 35 U 85 55 u 117 75 • 149 95 µ 181 B5 Õ 213 D5 õ 245 F5
[SYN] 22 16 6 54 36 V 86 56 v 118 76 – 150 96 ¶ 182 B6 Ö 214 D6 ö 246 F6
[ETB] 23 17 7 55 37 W 87 57 w 119 77 — 151 97 · 183 B7 × 215 D7 ÷ 247 F7
[CAN] 24 18 8 56 38 X 88 58 x 120 78 ˜ 152 98 ¸ 184 B8 Ø 216 D8 ø 248 F8
[EM] 25 19 9 57 39 Y 89 59 y 121 79 ™ 153 99 ¹ 185 B9 Ù 217 D9 ù 249 F9
[SUB] 26 1A : 58 3A Z 90 5A z 122 7A š 154 9A º 186 BA Ú 218 DA ú 250 FA
[ESC] 27 1B ; 59 3B [ 91 5B { 123 7B › 155 9B » 187 BB Û 219 DB û 251 FB
[FS] 28 1C < 60 3C \ 92 5C | 124 7C œ 156 9C ¼ 188 BC Ü 220 DC ü 252 FC
[GS] 29 1D = 61 3D ] 93 5D } 125 7D (n/a) 157 9D ½ 189 BD Ý 221 DD ý 253 FD
[RS] 30 1E > 62 3E ^ 94 5E ~ 126 7E ž 158 9E ¾ 190 BE Þ 222 DE þ 254 FE
[US] 31 1F ? 63 3F _ 95 5F [DEL] 127 7F Ÿ 159 9F ¿ 191 BF ß 223 DF ÿ 255 FF
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); //Ensure LED is off
Serial.begin (38400); //initiate serial monitor at 38400bps
}
void loop() {
// put your main code here, to run repeatedly:
if (Serial.available() > 0) {
String incomingString = Serial.readString();
//1 = on and 0 = off
if (incomingString == "on") {
digitalWrite(LED_BUILTIN, HIGH); //Turn on LED
Serial.println ("LED on");
} else if (incomingString == "off") {
digitalWrite(LED_BUILTIN, LOW);
Serial.println ("LED off");
} else {
Serial.println ("invalid input");
}
}
}
If we want to send a string, we will pass the string to the ‘write’ function and it will send it. The function
also returns the length of the string, and we would use this variation as below:
int bytesSent = Serial.write("hello"); //send “hello” and return 5
You may want to check if there is space in the buffer before transmitting the data and you can do this
with the ‘Serial.availableForWrite()’ function.
We have used ‘Serial.print’ and ‘Serial.println’ regularly in previous books of this series. The
difference between ‘Serial.print’ and ‘Serial.println’ compared to ‘Serial.write’ is that the
‘print’ and ‘println’ functions transmit data in human readable format. This need not be true of
‘Serial.write’.
What is Bluetooth?
Bluetooth is a short-range wireless technology that was designed to do away with the computer’s jungle
of wires. The technology is highly commoditized, meaning it is extremely cheap and you can find
Bluetooth functionality in all sorts of devices such as headsets, speakers, keyboards, and the like.
Bluetooth components can be used as master and slave pairs to provide 2-way serial connections in
place of cables, and this is how we will use the technology. We should be able to achieve a maximum
range of 20m.
We will connect the HC-05 to the Arduino board using the layout in Figure 1-7.
Figure 1-7: The Breadboard Layout to Connect the HC-05 to the Arduino
Image source: Created with Fritzing
We will power the Arduino with a battery connected to the Power input barrel as we want the system to
be remote from our PC. You will see LED indicators on the HC-05. The next task will be to pair the HC-
05 to your PC. What follows are the steps for Bluetooth pairing on a Windows 10 PC. If you do not have
built in Bluetooth, then the same process applies to a Bluetooth USB adapter.
In Windows 10 you will need to add a new Bluetooth device. Select Settings > Bluetooth & other
devices as in Figure 1-8.
Figure 1-8: Windows 10 Setting Screen
Image source: Microsoft Windows
Select Add Bluetooth or other device. You will be presented with the screen as in Figure 1-9. Select
the Bluetooth option.
Summary
In this chapter we took deeper look at the Serial Monitor and how to input data from an external source
in the form of a byte stream and standard text. You were introduced to the ASCII coding and asked to
look closer at the RS-232 specification. You learnt how to use I/O pins 0 and 1 on the Arduino Uno for
serial communications and use a Bluetooth module to pair with a PC to invoke wireless serial
communications.
Chapter 2: Interrupts
In this chapter we will look a processor interrupts. The ability to handle interrupts is a fundamental
feature of all microcontrollers and microprocessors. Understanding what an interrupt is and how it
works is an essential skill for the electronic engineer. Essentially, an interrupt is a signal from a source
external to the processor that stops the processor executing its current task to start work on a specific
block of code associated with that interrupt. When that code is finished, the processor will resume its
original task.
You can appreciate the uses this has for emergency action, the change direction of a vehicle, an
emergency stop or trigger for an environmental alarm.
Pseudo Multitasking
The use of interrupts can give your Sketches the feeling of being able to multitask. You cannot achieve
full multitasking with the Arduino as it has a single processing core. Having the Sketch suspend a task
to process an interrupt will give that you a multitasking feel. The same is also true of timed interrupts
that we will look at later in this chapter.
Another factor to bear in mind is that the Arduino Uno can support 2 hardware interrupts. INT0 and
INT1 are mapped to I/O pins 2 and 3 respectively.
//Define colors
const int RED = 11;
const int GREEN = 10;
const int BLUE = 9;
//Attach interrupt
//Switch press will be rising edge due to Schmitt Trigger inversion
attachInterrupt(digitalPinToInterrupt(2), changeColor, RISING);
}
void loop() {
//Brighten LED
for (int i = 0; i <= 255; i++) {
analogWrite (color, i);
delay (10);
}
//Dim the LED
for (int i = 255; i >= 0; i--) {
analogWrite (color, i);
delay (10);
}
delay (500);
}
void changeColor () {
//Turn off current LED
digitalWrite (color, LOW);
//Select a new color
if (color == RED) {
color = GREEN;
} else if (color == GREEN) {
color = BLUE;
} else {
color = RED;
}
}
Timed Interrupts
Timed interrupts are simply tasks that execute periodically. The Arduino Uno has 3 timers built into
the system and we use one of these timers to control timed interrupts. In fact, we have used these timers
for many experiments in previous books. The PWM capable pins are controlled by timers when used
for PWM actions. The ‘delay’ function also uses a timer.
The 3 timers on board the Arduino Uno are referred to as Timer 0, Timer 1 and Timer 2. Timed
interrupts use Timer 1. However, the PWM pins 9 and 10 also use this timer so these pins cannot be
used for PWM tasks when using Timer 1. The following PWM pins are associated with the following
timers:
• Timer 0 - 6 and 5
• Timer 1 - 9 and 10
• Timer 2 - 11 and 3
Timer 1 is 16-bit and so can count from 0 to 65535. When 65535 is reached, the counter resets to 0. The
system uses a clock divider to control the rate of count. Without the clock divider, the counter would
clock at 16 million cycles per second. Timer 0 and Timer 2 are 8-bit and so the count is 0 to 255.
We can use a 3rd party library called ‘TimerOne’ that deals with all the complexity of using the timer
such that we can simply set the trigger period for the interrupt routine. To load the library, select
Sketch > Include Library > Manage Libraries. From the resultant window, search for ‘TimerOne’
and then click install.
In Experiment 5, we will use a timed interrupt to flash an LED every second while the main ‘loop’
measures temperature and humidity using a DT-11 sensor. We can also use a hardware interrupt to
toggle between Fahrenheit and Celsius.
#include <TimerOne.h>
/*
This Sketch was written with SimpleDHT version 1.0.14
The code may need to be modified if other versions are used
*/
#include <SimpleDHT.h>
// for DHT11,
// VCC: 5V or 3V
// GND: GND
// DATA: 8
int pinDHT11 = 8;
SimpleDHT11 dht11(pinDHT11);
void setup() {
// set I/O pins
pinMode (LED, OUTPUT);
//Attach interrupt
//Switch press will be rising edge due to Schmitt Trigger inversion
attachInterrupt(digitalPinToInterrupt(2), changeUnit, RISING);
//Initialize Serial monoitor for display
Serial.begin (9600);
//Set up timed interrupt
Timer1.initialize (1000000); //set to 1 second in microseconds
Timer1.attachInterrupt(flashLED); //runs flashLED on timed interrupt
}
void loop() {
// read temperature and humidity
byte temperature = 0;
byte humidity = 0;
int err = SimpleDHTErrSuccess;
if ((err = dht11.read(&temperature, &humidity, NULL)) !=
SimpleDHTErrSuccess) {
Serial.print("DHT11 read fail");
Serial.println ("err="); Serial.print(err); delay(1000);
return;
}
void changeUnit () {
//Toggle C and F
if (units == 1) {
units = 0;
} else {
units = 1;
}
}
void flashLED () {
digitalWrite(LED, !digitalRead(LED)); //Toggle LED
}
With respect to the DHT11, we need to pass the ID of the pin that the data lead is connected to.
int pinDHT11 = 8;
SimpleDHT11 dht11(pinDHT11);
Since the focus of this experiment is to investigate timed interrupts, we will focus on that code first. We
need to initialize the timer by calling the function below and attach the name of the function that will
be executed. Here, we set the timer to 1000000 microseconds which is 1 second and the ‘flashLED’
function will be executed each second.
Timer1.initialize (1000000);
Timer1.attachInterrupt(flashLED);
The function ‘flashLED’ simply inverts the current state of the pin to which the LED is connected.
A hardware interrupt is used to change the units from Celsius to Fahrenheit and vice versa. We do not
need to review this code.
The remining code is focused within the main ‘loop’ function and controls the DHT11 sensor.
First, I need to point out that I used version 1.0.14 of the ‘SimpleDHT’ library, and the Sketch was
written around this. There are differences in the code associated with some library versions so check
the documentation using the ‘more info’ link if things do not work. If in doubt, use version 1.0.14.
The code reads the temperature and humidity from the DHT11 sensor. The code for this comes directly
from the library examples. In version 1.0.14 of the library, the code checks to see if there is an error
when reading the sensor and displays the error code if one occurs. If there is no error, the temperature
and humidity values are inserted in the respective byte variables. The ‘&’ symbol in front of the
temperature and humanity values means we are passing a reference to those variables so they can be
used in the ‘dht11.read’ function. The ‘return’ statement means that the current function (which is
the main ‘loop’) will terminate at that point and start over.
The code only then needs to print the results to the serial monitor. The resultant output from the Sketch
is shown in Figure 2-11.
Figure 2-11: The Serial Monitor Output for Experiment 5
Image source: The author
Summary
In this chapter you learnt how to debounce a switch with hardware using a Schmitt Trigger and RC filter
circuit. You explored reasons why software debouncing is not appropriate for debouncing a switch for
interrupts. You learnt how to control the I/O pin and timed interrupts supported by the Arduino Uno.
You learnt about the 3 timers on the Arduino Uno and how they have multiple purposes such controlling
PWM and how this can constrain the use of PWM pins or the timers for interrupts. You also looked at
how to load external libraries and how they make easy the programming of sophisticated sensors such
the DHT11 and Arduino features such as timers.
Chapter 3: The I2C Bus
In this series of books, we have looked at how to interface sensors using serial communications,
Bluetooth and with dedicated libraries. What we have not yet explored is how the Arduino can utilize a
standard protocol designed for interworking between electronics devices. The I2C protocol (pronounced
‘eye two see’ or ‘eye squared see’) was designed for this task and is the heart of many sophisticated
modules.
#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_SSD1306.h>
void setup() {
Serial.begin(9600);
delay(3000); // wait for console opening
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
for (;;); // Don't proceed, loop forever
}
pinMode (SETCLOCKPIN, INPUT_PULLUP);
if (! rtc.begin()) {
Serial.println("RTC not found");
while (1);
}
if (digitalRead(SETCLOCKPIN) == LOW) {
Serial.println("setting");
if (rtc.lostPower()) {
//Set time to that when program was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Following line sets the RTC with an explicit date & time
// for example to set January 27 2017 at 12:56 you would call:
// rtc.adjust(DateTime(2017, 1, 27, 12, 56, 0));
}
}
void loop() {
Next, we define a constant ‘char’ array to hold the days of the week. This is a 2-dimensional array to
hold the seven days, each expressed in 3-character format. This necessitates an array of size of 7 x 4.
The Arduino programming language is based on c++ and when we define each day, 3 spaces in the array
are needed for the day of the week and c++ adds the null termination character ‘\0’. A space is needed
for this character or the compiler will throw an error.
char daysOfTheWeek[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
"Sat"};
The other constants are self-explanatory. We will use I/O pin 2 to set the clock after the Sketch is first
uploaded. We define the screen width and height and state that we will use the Arduino’s reset button
to reset the display.
const int SETCLOCKPIN = 2;
const int SCREEN_WIDTH = 128;
const int SCREEN_HEIGHT = 64;
const int OLED_RESET = -1; //uses Arduino reset button
A block of code is placed in the ‘setup()’ function to check for the presence of the display on the I2C
bus and we pass the I2C address as an argument. If the display is not found we effectivity stop the code
there with a loop that runs forever (‘for (;;)’)
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
for (;;); // Don't proceed, loop forever
}
We also want to check for the presence of the DS3231 RTC. If the DS3231 is not found, then the code
will also stop at this point with another way of implementing a forever loop with a ‘while’ loop.
if (! rtc.begin()) {
Serial.println("RTC not found");
while (1);
}
If the DS3231 is found, we check to see if pin 2 of the Arduino is ‘LOW’, and if it is, we set the time. The
function ‘rtc.lostPower()’ returns ‘TRUE’ if the DS3231 as lost track of time. We can then execute
a special set of arguments to apply the date and time that the Sketch was compiled (‘F(__DATE__),
F(__TIME__)’). This may still be a few seconds away from the real time, but close enough for our
experiment.
if (digitalRead(SETCLOCKPIN) == LOW) {
Serial.println("setting");
if (rtc.lostPower()) {
//Set time to that when program was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Following line sets the RTC with an explicit date & time
// for example to set January 27 2017 at 12:56 you would call:
// rtc.adjust(DateTime(2017, 1, 27, 12, 56, 0));
}
}
The main ‘loop’ function focuses on getting the current time and displaying the result. We create an
object of the ‘DateTime’ class and get the current time from the DS3231.
DateTime now = rtc.now();
We then set the text size for the screen. The smallest is 1 and the largest 3. We move the cursor to where
we want to place the text and set the color to a redefined value that turns on the pixel. The constant
‘SSD1306_WHITE’ does this. It implies the color white, but the screen will illuminate with whatever
color the LEDs are. The constant ‘SSD1306_BLACK’ turns off the LEDs.
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE); // Turns on pixel
display.setCursor(65, 50);
We want to print the date and time in a recognizable format. We could print the returned values from
‘now.day()’ and ‘now.month()’, but this would not print leading zeros if they are needed. We can
used the ‘sprint()’ function to do that formatting using the precursor ‘%02’ to print 2 figures with a
leading zero if the value is less than 10. Within the ‘sprintf’ function, will create a string called ‘date’
with the day and month separated with the ‘/’ symbol. We send this string to the SSD1306 display using
the ‘display.print’ function.
sprintf(buf_date, "%02d/%02d", now.day(), now.month());
display.print(buf_date);
Note that ‘display.print’ does not actually display the text. It prepares the display to show this text
and a trigger is needed later.
The time is treated in a similar way to the date to position the text, size the text and format the text.
Finally, we trigger the display to show the date and time using the function below:
display.display();
An image of my setup is shown in Figure 3-10 and a video of this project can be seen here
(https://youtu.be/Yh-CEspwd6Y).
Figure 3-10: The I2C Digital Clock
Image source: The author
Summary
In this chapter you learnt about the I2C bus, its history and how it works. You learnt what pins on the
Arduino Uno can be used for I2C communications. You were made aware to use manufacturers
datasheets to understand the I2C addresses for electronic components and modules as well as choosing
the best pullup resistor values. You learnt about the DS3231 Real Time Clock and the 12864 OLED
display that uses the SSD1306 driver chip. You learnt how the ‘Wire.h’ library and other libraries make
programming I2C modules much easier.
Chapter 4: The Serial Peripheral Interface Bus
This is a third communications method that can be used between peripheral devices and the Arduino
board. The other 2 being serial UART and I2C. The Serial Peripheral Interface, or SPI (often pronounced
‘spy’) Bus for short is covered in this chapter.
Table 4-3: Some Differences between the SPI Bus, I2C and Serial UART
Table 4-4: The MCP4131-103E/P Register Value for the Pot’s Wiper
#include <SPI.h>
void setup() {
pinMode (CS1, OUTPUT);
pinMode (CS2, OUTPUT);
pinMode (POT1, INPUT);
pinMode (POT2, INPUT);
SPI.begin();
Serial.begin (115200);
}
void loop() {
for (int i = 0; i <= 128; i++) //7 bits addressing for the pot wiper
{
//increase resistance of pot 1
digitalWrite(CS1, LOW);
SPI.transfer(address);
SPI.transfer(i);
digitalWrite(CS1, HIGH);
voltage1 = analogRead (POT1) / 1023.0 * 5.0;
//decrease resistance of pot 2
digitalWrite(CS2, LOW);
SPI.transfer(address);
SPI.transfer(128 - i);
digitalWrite(CS2, HIGH);
voltage2 = analogRead (POT2) / 1023.0 * 5.0;
Serial.print ("Pot 1: ");
Serial.print (voltage1);
Serial.print ("V ");
Serial.print ("Pot 2: ");
Serial.print (voltage2);
Serial.println ("V ");
delay(200);
}
delay(500);
for (int i = 0; i <= 128; i++)
{
//decrease resistance of pot 1
digitalWrite(CS1, LOW);
SPI.transfer(address);
SPI.transfer(128 - i);
digitalWrite(CS1, HIGH);
voltage1 = analogRead (POT1) / 1023.0 * 5.0;
//increase resistance of pot 2
digitalWrite(CS2, LOW);
SPI.transfer(address);
SPI.transfer(i);
digitalWrite(CS2, HIGH);
voltage2 = analogRead (POT2) / 1023.0 * 5.0;
Serial.print ("Pot 1: ");
Serial.print (voltage1);
Serial.print ("V ");
Serial.print ("Pot 2: ");
Serial.print (voltage2);
Serial.println ("V ");
delay(200);
}
}
From the list of constant declarations, the register value for the pot’s wiper is the most significant and
we discussed why the value is 0.
const byte address = 0x00;
We want to convert the analog input value for each pot back into a voltage, so we need 2 floating point
variables.
float voltage1; //volts for pot 1
float voltage2; //volts for pot 2
In the ‘setup’ function we assign the Arduino I/O pins and start the Serial Monitor to display the wiper
voltages. A fast baud rate is selected to empty the buffer quickly. We also initialize the SPI Bus with the
line of code below.
SPI.begin();
In general, you can use the basic ‘begin’ function that has no arguments. This will load the default SPI
Bus values. For an Arduino Uno this will be a bus data rate of 5Mbps, Mode 0 and the Most Significant
Bit (MSB) first. Since most SPI device support bus speeds of more than 5Mbps, this command will be
fine. Mode 0 and MSB first are also common then there is no need for any other command. If, however,
you wanted more control you can substitute the line ‘SPI.begin()’ with the following:
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));
This will run the bus at 10Mbps, the maximum speed of the MCP4131-103E/P.
In the main ‘loop’ function, we step through each resistance value with a ‘for’ loop until the maximum
is reached and then step down again to 0. The MCP4131-103E/P supports 7-bit resolution so there are
128 steps.
With each step, we transfer a new value to the MCP4131-103E/P by first sending the register value and
then the data.
digitalWrite(CS1, LOW);
SPI.transfer(address);
SPI.transfer(i);
digitalWrite(CS1, HIGH);
Reading the value from the wiper, we then convert this back to a voltage value. We need to use floating
point arithmetic. The value from the Arduino’s ADC is an integer so we must append 1023 and 5 with
‘.0’ to force floating point arithmetic. Otherwise, the resultant value will always be 0.
voltage1 = analogRead (POT1) / 1023.0 * 5.0;
Summary
In this chapter you learnt about the Serial Peripheral Interface Bus and compared its features with the
I2C and Serial UART technologies. You learnt how to program the SPI bus using the Arduino library
and what Arduino I/O pins are set aside for SPI communications. You learnt about the MCP4131-
103E/P digital potentiometer and how to program it using the SPI bus.
Chapter 5: The AREF and IOREF Pins
You may have noticed two more Arduino pins that we have not yet covered. In this short final chapter,
we will look at the AREF and IOREF pins and conclude the book with a simple experiment.
The IOREF pin is easy to deal with as it provides a reference for a shield of the working voltage of the
board. It is not an I/O pin as such and connecting a voltage to this pin will do nothing. The operating
voltage of an Arduino Uno is 5V and so the IOREF will be 5V. An Arduino Due works at 3.3V and the
IOREF is set to 3.3V. There is nothing more really to say about the IOREF pin.
AREF means Analog Voltage Reference. It allows us to feed a reference voltage into the board which we
can use as an alternative maximum voltage for the Analog to Digital Converter (ADC). By default, the
ADC is set to read 0V as the minimum and 5V as the maximum. It has a 12-bit resolution and so a digital
reading of 0 correlates to 0V and 1023 correlates to 5V. Let us say we are working with 3.3V logic. The
maximum voltage we can present to an analog input will be 3.3V. This means the maximum ADC value
will be ~675. We are losing one third of the ADC’s resolution. The AREF pin allows us to redefine the
resolution of the ADC. By attaching a voltage of 3.3V to the AREF pin, 0V will be represented by 0 and
3.3V by 1023. It is this functionality we will test with a simple experiment.
void setup() {
Serial.begin (9600);
}
void loop() {
int val = analogRead(pot);
Serial.println (val);
delay (500);
}
Summary
In this chapter you learnt about the functions of the AREF and IOREF pins and how the AREF pin can
be used to increase the resolution of the Analog to Digital Converter.
Epilogue
We have come to the end of this part of the journey. I feel this book is a great companion to Book 1 of
this series (First Steps with Arduino) as it builds on the essential basics learnt from that book. The skills
learnt here can be applied to projects for the hobbyist and the most complex professional developments
alike. They are highly relevant for anybody looking for a career in electronics or just looking to have
fun.
In the subsequent books in the series, we will use our knowledge gained to look at other focus areas and
create ever more challenging, creative and interesting projects. Whatever your goals are, I hope to have
inspired you and helped you some way to achieving those goals.
If you like this book and the series of books, please leave a review on the Amazon website. For the latest
news on other books in the series you can following my Facebook page, Twitter or follow me as an
author on Amazon.
About the Author
Gary Hallberg has over 34 years of experience in the telecommunications and data communications
industries. He started his career working with circuit switched voice and data technologies in PDH and
SDH environments and then moved on to work with early packet switched networks using Frame Relay
and ATM. His career shifted focus to IP routing when the Internet started to grow, designing large scale
service provider networks using leading edge equipment from vendors such as Juniper and Cisco. Gary
attained Cisco Certified Professional status during this time. Presently, he is working for a global
equipment vendor with a focus on Ethernet networks and is at the forefront of network evolution,
providing infrastructures for SD-WAN, Network Functions Virtualization, SDN and 5G backhaul. Gary
has a Bachelor of Engineering degree in electronic engineering and a Master of Philosophy degree in
robotics and manufacturing.