100% found this document useful (1 vote)
304 views323 pages

ARDUINO Beginner To Advanced

Uploaded by

mouwaka
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
304 views323 pages

ARDUINO Beginner To Advanced

Uploaded by

mouwaka
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 323

ARDUINO

Beginner to Advanced
ARDUINO
Beginner to Advanced

Employing Arduino with Proper Understanding

HappyLogic
Copyrights © All Rights are Reserved.
About the Book

Even though, there are many titles available for learning Arduino from
programming perspective mainly, but it is highly imperative to consider
various hardware, code management, solution architecture and other
programming concepts for being successful at designing medium to large
scale solutions. Arduino itself, has made programming different
microcontrollers, and evaluating designs quite easy; additionally,
understanding the hardware, programming fundamentals and code
architecture are key areas for building stable and extensible solutions
utilizing fundamental building blocks already provided by the software
development kit.
Hence, this book is aimed at acting as a starting point for beginners,
hobbyists, and junior designers – guiding towards designing various
hardware projects utilizing Arduino, while also guiding through to handling
projects with varying difficulties involving various multiple elements.
Afterall, today’s microcontrollers are much more powerful and capable than
most personal computers used during the nineties, though project
complexities in real-world scenarios have also grown in and beyond
equivalent proportions. Still, it requires quite an undertaking to utilize all
the computing and hardware resources in the majority of modern
microcontrollers at full capacity.
Although, selection of microcontrollers must be totally based on
requirements of target utility; mostly, modern applications are not just about
handling one or two tasks with controllers. Often in real-world applications
surplus computing and hardware resources are kept in designs for future
expansions without incurring massive hardware changes. Therefore, in
almost all the modern applications, expansion with system stability remains
a constant concern. Without designing applications with extensibility in
mind, stability concerns are almost always there to be dealt with –
sometimes through a painful process and debugging. Management of
fragility in designs is an ongoing concern that needs consideration early for
all the systems that are expected to grow over time. Due to inherent ease of
prototyping with Arduino, many a times best development practices are
rarely seen especially amongst new designers. These do not cause
difficulties during initial phases but become evident as systems grow in
complexity.
Efforts are made to keep this book concise and to the point; whereby,
initially, general programming concepts from C++ are discussed especially
those which readers are most likely to use and encounter in hardware
programming. Thereafter, Arduino development and hardware concepts are
discussed, along with Arduino general libraries, including design best
practices and further resources.

Associated resources with the book are available at GitHub link:


https://github.com/arduino-ba
About the Author

Having a continual programming experience of well over two decades


including but not limited to military and aviation-grade systems, the author
is adept at software system designs and architecture, especially
incorporating hardware targeted aspects involving microprocessors,
microcontrollers, Field Programmable Gate Arrays (FPGAs), and their
combinations to build larger-scale systems. Although, the author
commenced his learning journey in the field of programming with BASIC
language before turning 10 years of age, though it provided solid
foundational sense at the early age which helped him in furthering and
building upon the baseline ever-since. Thereby, the author has been attached
with learning, teaching, and applying his skills wherein possessing amicable
command in a variety of prominent programming languages and
environments including Assembly, C, C++, Java, C#, Python, MATLAB,
VBA, JavaScript, AWK and Shell Scripts. His core interests include:
Realtime Systems, Operating Systems including variety of Realtime
Operating Systems, Distributed Computing, Cloud Infrastructure, Secure
Programming, Web Applications, Artificial Intelligence and Computer
Vision.
After devoting a considerable time and effort in understanding and
applying knowledge in various domains of design, research, and
development; the author advocates that the modern computing technologies
require more of an artistic approach, creative imagination, and
developmental philosophies to utilize them effectively, rather than just
being knowledgeable in the fields of computing sciences. Given that the
advances in varied technologies have influenced our lives beyond any
specks of doubts and this trend shall continue shaping our times for any
foreseeable future, wherein bringing new knowledge arenas into spotlights
of reality while rendering presently sought-after skills obsolete quite rapidly
with the passage of time. Thereby, the author supports to promote more of
well-educated decision making instead of just being well-informed when it
comes to learning and applying technologies; and that cannot be achieved
without first understanding the technologies in essence; otherwise, we all
will keep getting mesmerized by magical wizardry of few tech leaders
which puts us at a greater risk of being driven by the technologies instead of
being empowered to drive them.
intentionally left blank
Dedication

This book is dedicated to Mr. Muhammad Awais Nizam, who guided,


educated, and gave me confidence to explore much beyond the existing
capabilities and incapacitating thoughts.
intentionally left blank
Table of Contents
Chapter 1: Introduction
What are microcontrollers?
The hardware aspects
Tooling
Programming languages
IoT – a big picture
Way-forward
Chapter 2: Revisiting C++
Compilation and Linking
Cross-compilation
Build automation
Setting-up the environment
Windows
Debian-based Linux Distributions
RedHat-based Linux Distributions
Code editor vs IDE
Our first program and build script
Source
Build script
Chapter 3: Revisiting C++ — Constants, Variables, Structures, and Flow
Control
Computing hardware basics
Processor
ROM
RAM
Instruction and Data Caches
Hardware Controllers
Direct Memory Access
Comments
Constants and variables
Value notations
Scopes
Type casting
Value vs Reference types
Operators
Assignments
Mathematical operations
Boolean logic
Boolean Bit-wise operations
Ternary operator
Operators’ precedence
Arrays
Standard I/O
Structures
Program execution flow control
Conditional statements – if, else if, else
Conditional statements – switch
Looping – for loop
Looping – while loop
Looping – do-while loop
Looping – break and continue
Blocks’ nesting
Challenge
Chapter 4: Revisiting C++ — Functions, Classes, Namespaces, and
Pointers
Functions
Recursive functions
Function variables
Functions with default parameters
Functions without inputs or return value
Functions with variable number of arguments
Function prototyping
Function overloading
Inline functions
Pointers
Getting addresses
Dereferencing
Pointers to functions
Pointer to character string
Pointer arithmetic
Typecasting
Classes
Constructors and destructors
Static functions
“this” pointer
Inheritance
Runtime Polymorphism
Namespaces
Type aliasing
typedef – keyword
using – keyword
A note on compiler directives and switches
#include
Macros
Conditional compilation
Splitting code into multiple files
Chapter 5: Understanding Basic Hardware
Power Supplies
Resistors
Diodes and LEDs
Inductors and relays
Capacitors
Transistors, FETs and MOSFETs
Analog to Digital Converters
Digital to Analog Converters
Sensors
Actuators
Chapter 6: Understanding Arduino
Environment setup
First look at the IDE
Libraries
Windows Remote Arduino with StandardFirmata
Default library functions
Library functions
First Arduino project
Splitting into multiple files
Chapter 7: Example Projects
Interfacing a keypad
Cyclic task execution
Way-forward
intentionally left blank
intentionally left blank
Chapter 1:
Introduction

N ot too long ago, it was sort of mandatory to have an IC (Integrated


Circuit) burner or programmer at hand if someone needed to work with
microcontrollers. Whereby, the customary practice would be writing code in
Assembly or C language, generating a binary file; then giving the binary
file to IC programmer software to finally burn the program binary into a
microcontroller that would be installed in a ZIF (Zero Insertion Force)
socket of the burner.

Figure 1. IC Programmer

IC programmers could either be IC specific, or the better ones supported


many different microcontrollers and EEPROMs (Electrically Erasable and
Programmable Read Only Memory). However, after programming the
device at least a minimum circuit was required to be patched to supply the
microcontroller with power, provide clock signal using an oscillator and
capacitors, and adding a simple reset circuit – all this to just get the program
in running state. And for other input and output elements of the
microcontroller, they would further be connected through other hardware
components to complete a basic working circuit.

IC Burning devices are also available in the form whereby multiple ICs
are burnt in a single go. Their intended uses are in mass production.

In addition to just patching the minimal circuit for testing, sometimes


component selection itself becomes quite tricky in certain scenarios; for
example, when Serial Port RS-232 is required for communication with an
8051 microcontroller, external crystal oscillator is chosen to be of precise
value such as 11.0592 Megahertz; otherwise, you would only get garbage
values at both the ends of the communication channel. Still this kind of
development is a significant improvement over previous generations of
embedded solutions development.

Modern microcontrollers support multiple communication channels and


allow different internal devices to operate at different frequencies;
therefore, a variety of high and low speed communication channels can
be operated on a single chip.

Times have changed quite a lot, and much better options are now
available that speed up the overall development cycle and facilitate in
finding problems quite easily. Now, we no longer have to go through long
development cycles anymore, unless we have some specific requirements,
or we have to support some legacy systems. Arduino software development
kit and development boards, along with some other such solutions, are quite
special in this regard. It is one such thing that shortens developing and
prototyping cycle, facilitates quick idea validations, and even deploying
some quick solutions, alongside developing hobbyist-grade projects. Users
readily get pre-patched minimal circuits with quick and easy IC
programming facility to enjoy a head-start.
Developing understanding of underlying technologies plays a major role
in developing effective solutions. In order to develop an understanding in
the embedded world, we will first look at different concepts so as to bring
everyone on the same page before proceeding further with developing
solutions with modern tools.
What are microcontrollers?
Without going to reproduce a customary history lesson on
microcontroller evolution since late 1960s, it is pertinent to discuss their
characteristics and capabilities.

Figure 2. Controller circuit

Often confusing terms i.e., microcontroller and System on Chip (SoC)


are quite close to each other, and due to massive improvements in IC
designs, differences described are very blurry.
By common definition, microcontrollers were differentiated from
microprocessors; whereby, microprocessors used to specialize in running
general-purpose applications working in combination with external devices
and chipset including RAM (Read Only Memory), hardware IO (input and
output) and other peripheral devices. In contrast, microcontrollers have
remained embedded application specific having processing resources,
programmable memory, RAM, and limited IO peripherals on a single chip.
Recently, SoC devices have been gaining popularity partially due to
open-source projects and partially due to lowering prices of chip
manufacturing with advanced capabilities. SoCs can be regarded as
more powerful and advanced microcontrollers that have peripherals like
Wi-Fi, Ethernet, Bluetooth and perhaps other communication cores
provided on the same single chip – enhancing connectivity while also
providing more compute power as compared with generally available
microcontrollers.
In contrast with common belief, there are many 4-bit microcontrollers in
use these days, and recently due to lowering prices, increasing capabilities,
and increasing complex requirements, 32-bit microcontrollers are getting
into common use.
The hardware aspects
Arduino development kits are not the only solution in easy design,
prototyping and hardware development domain. In fact, many professional
and industrial grade solutions are available for quick prototyping and quick
development. For example, NXP, ST Microelectronics, and others provide
excellent development and evaluation boards.

Figure 3. S32K3X4EVB-Q172 Evaluation Board (Source: NXP)

However, the rule of thumb in hardware selection is to never try to hunt


a sparrow using a military tank.
Each piece of hardware is important in its own place and value – some
hardware devices are for industrial and rugged environments, some for
hobbyist-grade use, and some are for military and medical domain uses
where high reliability is a primary concern. Albeit the primary reasons for
selecting any hardware remain the availability of required number of inputs
and outputs, communication channels, supported communication formats,
available processing power and special considerations for security related
features.
For example, if there are design requirements to control four motors on
a robot simultaneously in synchronization, while also controlling five
actuators precisely, additionally there are cryptographic communication
concerns over the ethernet; thus, the hardware selection will surely consider
maximum load conditions, or possibly distribute the overall load on
multiple microcontrollers for easy handling and development. Although this
book is about Arduino, its software development kit and hardware boards;
but discussing hardware in general, broadens perspective since the
overarching concepts are universal. Additionally, learning of concepts is
paramount, as it eases switching to other, more or even less capable
hardware upon requirement. It would be nice to know that firmware
developed using Arduino codebase can also be executed on many other
microcontrollers.
Tooling
By “tooling” here, it refers to the set of tools that help in all the phases
of development i.e., writing code, converting the code into machine
instructions, and uploading the machine instructions to the target device.
Many tools work in harmony to produce desired resultant output.

Arduino is all about simplifying the development of embedded


applications through standardized hardware kits and simplified
programming environment.

Code compilation and uploading to target device is managed under-the-


hood by Arduino environment using various toolchains automatically. On
one hand it is quite fascinating and easy for new developers, but on the
other hand it limits learning about the toolchain itself. One can easily
defend this by maintaining that you do not need to know about something
that is already done beautifully for you at no extra cost. However, a little
knowledge about under-the-hood working can empower developers in
applying the same principles to other programming areas as well, and more
importantly, one is not completely locked-in into using the provided IDE
(Integrated Development Environment) at all the times.

Arduino IDE is one of good IDEs available – though it is simple and


lacks many powerful code development features that are commonly
available in other major software development environments, for
example, code IntelliSense and various environment customizations for
supporting focus, helping in long working times, and various
development pipelines.

Having better understanding of under-the-cover working can make


developers free in utilizing other tools that give more freedom and support
to developers; but more importantly, any IDE that matches individual’s
preferences can be utilized. For this reason, for example, there are
extensions available that can configure other development environments for
working with Arduino hardware and software development kit.

Any programming task consists of writing code in text files, using


compilation and build tools to create an executable file, running the
executable in target machine, checking the response, correcting
problems, and repeating this whole cycle. Better organized tooling and
better automation in this whole process can make lives easier; and
understanding the whole process empowers in developing better
solutions.
Programming languages
Although any microcontroller can be programmed using its Assembly
language – and that is the default and perhaps the only way when the
controller has extremely limited hardware resources, as the developers can
fully optimize instructions running in hardware. However, modern
microcontrollers enjoy much better hardware resources as compared with
older generations; in addition, modern application requirements are getting
too complex to be fully managed and extended with time completely in
Assembly language – though that is still an option. C language comes as a
natural next best fit for this task, alongside better code management in
medium to large-scale solutions using C++.
Performance comparisons show, many algorithms written in C language
to be more performant than the ones written in C++; though, utilizing
certain C++ features in developing microcontroller applications can prove
useful in terms of better code manageability without incurring heavy toll on
hardware resources. Management of code can easily go out of control if it is
not considered in the right sense as a practice.

It is pertinent to mention that there are successful efforts of running


applications written in other languages on microcontrollers as well.
For example, applications written in C# can be executed on some
high-end microcontrollers. Although, this can ease the lives of
developers who already know C#; however, under real-time constraints
options are very much limited to working with environments that do not
rely on Garbage Collection like C#. Therefore, sticking with developing
applications in Assembly, C and C++ gives a wider range of coverage,
and better utilization of processing resources available.
IoT – a big picture
Enabled by low-cost and high-volume manufacturing of microchips,
provision of advanced capabilities in low-cost microcontrollers, and
widespread availability of Internet infrastructure with enhanced
bandwidths; it has become much more cost effective and viable to connect
daily-use appliances, equipment, sensors, and other controllable devices
through the Internet – thus providing connectivity to things through the
Internet. Therefore, during recent years, IoT (Internet of Things) has been
getting a lot of traction – seeking to connect things over the Internet.
For many, IoT seems as if rebranding is done for the earlier “embedded
systems” having more capabilities, but for the majority it is totally a new
concept. IoT as a system of devices has many additional features and
improvements over traditional embedded systems like data collection, data
distribution and sharing, and drawing decisions based on data. A typical IoT
ecosystem looks conceptually like:
This typical depiction depicts having multiple devices embedded with
microcontrollers or SoCs, connected with on-premises or cloud-hosted
servers and utilizing services hosted in public or private cloud, constitutes
an IoT system.
Here, devices collect local sensor or other user interaction data and
transmit it to local or remotely hosted service, then the services draw
conclusions from data received from devices and other data sources like
weather information providers, and finally update devices accordingly. This
data driven approach provides greater benefits over conventional standalone
embedded systems.

In an IoT system – data is the king. The more relevant data that one has,
the better results can be achieved.
Anyone may look around and quickly find ways in which a lot of things
around them can be connected into a system through different means and
techniques, thereby automating daily routines – rendering lives easy and
efficient. For example, by learning electricity prices during different times
of a day, an IoT enabled washing system can plan jobs for times when
prices are lowest; a smart tagging system can remind you of picking up
required items when you are approaching your car in case, they are not with
you; and a voice command can rearrange smart and movable furniture and
adjust ambient lights as you would prefer while working or while watching
a movie according to preferences.

Just as a thought, recently populations are going through different issues


like Global Warming and resources getting scarce at certain places in the
World, like depleting water levels under the ground. Would it not be
beneficial to keep track of carbon emissions and resource utilizations at
personal and community levels to increase awareness and self-
accountability?

A certain class of devices in the whole system can be controlled to


perform specified tasks as and when commanded, while others can be used
to collect data to understand the world around us quantitatively. Similar
things have been and are still being done at industrial smart manufacturing
and management. This overall effect, when combined with huge data
transaction capabilities provided by 5G or newer generation of networks,
has an enormous potential – like performing critical operations remotely
while utilizing various sources of information, for example, remote
surgeries.
Way-forward
In the next chapters, we will first revisit C++ language features that any
hardware developer is most likely to encounter; if you are good at C++, you
can skip these chapters altogether or just skim through them. However, for
new developers it is imperative to start with basic programming and
compilation concepts as they come in handy when developing hardware
projects. These basic language concepts are quite limited features from C++
(which is a huge language) and are deemed necessary to handle low
powered and resource constrained devices – these are not specific to
hardware programming only and can be applied in general.
After covering language concepts, Arduino development environment
and basic hardware concepts are discussed. You might like to skip the
hardware chapter if you have enough understanding of basic hardware
components and concepts. Later, we will apply learnt skills in developing
some example projects. Best development practices are also discussed
along the way to help enhance coding and code management skills that
come handy in producing better readability of code and maintainability of
developed systems.
intentionally left blank
Chapter 2:
Revisiting C++

C ++ is an object-oriented language – wherein, “object-oriented” sounds


like a buzzword for people coming from hardware and other
backgrounds. In simple sense, objects are representations of anything like
real-world objects, or just a simple repeatable functionality etc., and the
style of code development that revolves around the orchestration and
interaction of different objects is an object-oriented style – whereby,
different objects holding their relevant data are placed in memory and only
required to act when required.
However, right now it is not the place to start with object-oriented
language concepts, as the aim here is to get to use the language in
developing hardware projects, or at least using relevant concepts in better
management of code and code reusability. Therefore, we will start with
simpler concepts first and build upon them. Afterall, programming is a
combination of tools, the better we can use right tools for right spots, the
better we would be able to enhance and make the final designs and products
better.

Commonly said, “C is a sea,” this implies that “C++” an ocean for it


being extending over C language. This depicts the actual nature of C++
having a lot of nuances and details that also give powerful ways of
developing large-scale applications.
Compilation and Linking
Like any other mainstream programming language, C++ code files are
plain text files – just for elaborating this idea, one can only use any software
that stores plain text files for developing code for C++; for example,
“notepad” is probably the simples text editing software on Windows
devices and it can be used to write C++ code, but Microsoft Word cannot be
used for this purpose because it saves files in binary or formatted text as it
has to remember text styles and support other text editing features. That
said, another quite common convention that we are going to use in this
book is storing files containing code with “ .cpp ” extension and the header
files with “ .h ” extension. If this does not make sense, nothing is to be
worried about, as things will make sense as we proceed further.
C++ is quite good at working alongside C and Assembly language
despite having higher-level language constructs. The process of converting
code written in higher language into machine code is referred to as
compilation, and the process of joining the generated machine code with
other machine code into making a runnable executable is called linking.
Since in microcontroller development it is quite common to use GCC (GNU
Compiler Collection) toolchain for many available microcontrollers in the
market, and GCC compilers are also available for working on common
desktop operating systems, we will be using the same while learning the
concepts.
Figure 4. Compilation and linking
Figure above depicts an overall process of compilation and linking with
other code. Here we are using “ .cpp ” extension to represent C++ code files
which are also called compilation units, “ .c ” represents code files written in
C language and “ .asm ” represents code written in Assembly language. C
and C++ compilers and Assembler generate object files having “ .o ”
extension, which are combined and linked with other standard libraries by
the linker to generate the final executable. The generated executable can
finally be run on the target system for which it has been generated.
Cross-compilation
Cross compilation is a term used when the target machine for which
executable is being generated is not the same machine on which
compilation is being done. For example, writing, compiling, and linking
code on a machine running Linux Distribution or Windows and the
generated executable is required to be run on a mobile or an embedded
device would fit the definition of cross-compilation. In development for
low-powered devices like mobile phones and microcontrollers, cross
compilation is the way to follow for having limited computing power and
resources on target device.
Therefore, cross-compilation toolchain is required, and Arduino
certainly uses the same for generating executable for running on an Arduino
compatible hardware. GNU and other projects have been developing cross-
compilation toolchain, however, we would be using GNU Compiler
Collection to build and run our applications – just to have common
experience throughout the book.
Build automation
In real-life scenarios, codebase for any application having at least
moderate complexity consists of considerable number of source files or
compilation units. Manually compiling all the files and linking them with
each other and other libraries used in application development can become
quite a cumbersome task; above that when many iterations are required to
be done in different source files within or without compressed environment
can quickly drain any developer. As the complete compilation process is
quite predictable and repeatable; therefore, the build process can be
completely automated through scripts or other methods.
There are many readily available build tools, for example, “MS Build”
in Visual Studio and “Make” for Linux distributions – though it can also be
used on Windows as well. Additionally, custom scripts can also be
developed if there are some complex requirements. However, we are going
to use Make utility in our examples – although it is an old utility and newer
alternatives are available; but it is quite sufficient for our purpose, and it is
quite commonly used till date. Although IDEs generate build automation
files automatically, we would make these files by ourselves in simpler
projects to develop an understanding.
Setting-up the environment
To have a uniform development environment, we would be working
with open-source tools for code editing and building. Visual Studio Code is
an open-source code editor, but in all the definitions of an IDE (Integrated
Development Environment) it is very much an IDE – and the best part is
that it is available for all the major desktop operating systems.
Windows
Windows introduced Windows Subsystem for Linux with Windows 10
and has been improving since then. For the sake of uniformity with other
operating systems, we would be using it for developing applications. By
default, it is disabled and must be enabled for having Windows to install
relevant drivers and support for running Linux distributions through
Microsoft App Store. Thereafter, we would be using Linux distributions for
code compilation.

For enabling WSL:


1. Go to settings. It can be done through the Start Menu.
Figure 5. Settings option in the Start Menu
2. In the search box, type “programs and features.”

Figure 6. Searching for Programs and Features in Settings

3. Select “Turn Windows features on or off” option, this will open


the old and quite familiar window.

Figure 7. Enabling Windows Subsystem for Linux


4. From the list, enable “Windows Subsystem for Linux” and click
“OK.” This will install the Linux subsystem while restarting
your device on requirement.

Now we can install and run Linux environment – just open the Microsoft
Store application and search for Linux distribution. There are many
distributions available through app store to run on WSL, just for the sake
of an example, search for “ubuntu.”

Figure 8. Ubuntu in Microsoft Store

After installation, run the app from the Start Menu. In the first run it will
go through installing base packages, making the environment ready for use.
Figure 9. Ubuntu running in WSL environment

Thereafter, we can install Visual Studio Code by downloading from


Visual Studio website. Its installation procedure is straightforward, just like
other Windows applications. VS Code can now be run from WSL
environment with simple command“ code [path-to-file(s)] .”

Figure 10. Creating a directory and opening it in VS Code

After installing the VS Code Server, the target folder is opened in VS


Code.
Figure 11. VS Code

This completes our applications installation on Windows; however,


installation of toolchain inside WSL environment is covered under next
steps, as Ubuntu is a Debian based, you can follow the steps given under
specified section.
Debian-based Linux Distributions
While working inside a Debian based Linux distribution like Debian or
Ubuntu, the default package manager is available to install compilation
tools.

sudo apt-get install gcc g++ make -y

For our simple learning path, the only required tools are gcc for
compiling C code files,
g++ for compiling C++ files and the make utility for introducing the build
automation. After installation, check their versions just to check if the tools
are installed successfully.

gcc --version

g++ --version

make --version

Each would display the installed version of the tool.

user@machine:~$ gcc --version

gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

Copyright (C) 2019 Free Software Foundation, Inc.

user@machine:~$ g++ --version

g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0

Copyright (C) 2019 Free Software Foundation, Inc.


user@machine:~$ make --version

GNU Make 4.2.1

Built for x86_64-pc-linux-gnu

Copyright (C) 1988-2016 Free Software Foundation, Inc.

Majority of the output is trimmed in above example uses, your systems


might display other outputs or versions available on your environments.
The only thing is to have available the latest versions at the time of your
using.

In the end, VS Code can be installed by following the instructions


available on
Visual Studio site.
RedHat-based Linux Distributions
The RedHat based older distributions use rpm as the package manager
and the newer ones are using dnf as the package manager. Depending on
the distribution, the following commands can work.

sudo rpm install gcc g++ make

Or, on dnf -based package management environments:

sudo dnf install gcc g++ make


As our compilation tools, we only require gcc for C, g++ for C++ and
make for build automation. After installation, check their versions to verify
if the tools are installed successfully.

gcc --version

g++ --version

make --version

Each would display the installed version and license information of


respective tool.

Installation of VS Code can be done according to package manager and


the instructions are available online at Visual Studio site – just search for
“VS Code” on the search engine and go to official Microsoft site for
Visual Studio Code and follow the instructions.
Code editor vs IDE
Code editing itself is a simple task of simply editing the text files and
many free tools are available – Windows has default text editor Notepad , and
in other desktop OS environments there are many options available like vim
and gedit , additionally there are tools like Notepad++ that can be used for
better editing experience. The easier it is to visualize code through syntax
highlighting and fonts, while also facilitating switching between code parts;
one can infer that the better environments help in producing better quality
code.
On the other hand, an IDE is a fully Integrated Development
Environment wherein all the development related activities like code
editing, building, running, and debugging can be done without leaving the
environment – for example, NetBeans, Eclipse, and Visual Studio. As we
are using VS Code in our learning here; it can be fully configured to work
like any other IDE due to its extensibility – though we will be using it as it
is, for the aim is to develop a little bit of understanding regarding the tools.

With code editors getting better and better with time, distinction between
them and IDEs has become quite blurry within the recent past. Many
code editors are quite capable and extensible enough to provide all the
features an IDE provides.
Our first program and build script
To evaluate our environment, let us write simple code and run it. For
organization purposes, all the examples are being kept inside the parent
folder “ arduino-ba-examples ,” first let us create it.

mkdir arduino-ba-examples

Then to work inside it, we will change current working directory to it.

cd arduino-ba-examples

Similarly, we will create folder named by each chapter number inside


the parent folder – like for our example:

mkdir Chapter-02

Let us now open this folder with VS Code

code Chapter-02
Now we can completely work inside VS Code to create files and even
accessing command shell inside the editor. For accessing the terminal,
select “New Terminal” option inside the “Terminal” menu.

Figure 12. Terminal access inside VS Code


Source
Our first example is a quite simple “Hello World!” application – a
customary first application in programming books. Let us create a new file
from inside the VS Code and name it “ HelloWorld.cpp ”

Figure 13. File creation

Now write the following few lines in the file and save it.

#include <iostream>
int main() {

std::cout << "Hello World!" << std::endl;

C++ programs start execution from main() function. Let us compile and
run it. Open the terminal and type:

g++ HelloWorld.cpp -o MyProgram

It will compile HelloWorld.cpp and generate MyProgram as the executable


output as the -o switch tells the compiler of the output file name. Let us run
the application in the terminal by:

./MyProgram

The application runs and displays what we expect.

Figure 14. Example output

We would be following the same scheme of arranging the code


throughout the rest of the examples.
Build script
Our example is quite simple and can be simply built by directly
invoking the compiler. However, to demonstrate using a build script
instead, we can create another file for this purpose. As our build automation
tool is make utility and we can save our build script in any file, however, the
default file name used by make is Makefile , therefore we will stick with this
name.
Let us create our Makefile inside the same directory we have our source
file placed in, and write the following lines in it:

# This is the default build target

default:

g++ HelloWorld.cpp -o MyProgram

clean:

rm MyProgram

“# ” denotes a comment line in the script. The Makefile consists of a


single or multiple targets, when no target is specified while invoking make
command, it is the first target that is executed by the make utility. In this
case we have two targets named “default” and “clean.” A target can also
describe other targets as its dependencies, so that the build system should
build the dependencies before building the target, these dependencies can
just be listed after the colon symbol. The following line (or lines) consists
of commands for specified target building – the important thing to keep in
mind is to indent the commands with tabs.
Although this is quite a simple Makefile for our simple project, a Makefile
can contain many other elements like variables and can be quite big in large
projects – it is there where their usefulness can be experienced in full.
Now, let us use our Makefile by simply typing the command:

make

As we are using the default name for our Makefile , we do not need to
provide a file name while running the make utility.

Figure 15. Building the project with make

This builds the application – just like we built it manually; additionally,


we can clean up the built executable by running the second target in our
Makefile .

Now that we have built and run our first application – we can build up
on simple concepts towards creating much more complex applications.
Final code can be accessed at:

https://github.com/arduino-ba

There are various build systems readily available though many are not
cross-platform, CMake endeavors to provide a uniform cross-platform
build experience.
intentionally left blank
Chapter 3:
Revisiting C++ — Constants, Variables,
Structures, and
Flow Control

I nwritten
coding – no matter which programming language the code is being
in, there are certain building blocks or the basic code elements that
are used in combination accordingly to create desired results or outputs.
Afterall, any software system can be simply viewed as taking some inputs,
doing some processing, and producing some outputs – mostly in repetitions.

Figure 16. Program flow depiction

Out of all the basic programming constructs, storing constant and


changeable values, accessing the required data, processing the data, and
controlling the flow of code execution are some of the most common
elements – common amongst all the mainstream programming languages.
In this chapter we will be discussing the basic building blocks of a C/C++
code. Before we begin, it remains quite helpful if basic hardware
understanding is developed.
Computing hardware basics
To fully understand the computing hardware, especially in presence of
modern and diverse computing devices – for specifically developing
understanding on how the generated machine instructions from code finally
get executed on a machine and how devices attached to a computer get
controlled, whether it be through a processor or a microcontroller; it itself is
quite a complex thing. We can suffice it by saying that the magic of digital
electronics plays its part in letting current flow through certain circuit
elements while stopping the current flow through other elements based on
logic evaluations and machine states. Though it is an oversimplification, it
boils down to it – really. Since our aim here is not to build a processor, we
will only try to understand what various hardware elements are there and
what role do they play – to help in understanding the code execution on a
machine.
To get the wheels rolling, while keeping things simple, we can visualize
core hardware elements in terms of following block diagram.

Figure 17. General-purpose computing device elements

A typical digital computing system can be simply described as a


combination of one or more processors, memory, and hardware devices –
and the machine code defines interactions with the hardware. In a
microcontroller, typically, all these block elements are part of a single chip
– though in a limited number with limited capabilities.
Processor
A processor being mainly responsible for executing machine code, it has
machine instructions parsing and execution pipeline at its core. Modern
processors try to speed up instructions’ execution by using higher clock
frequencies to speed up digital switching involved in instructions
processing, or by having multiple execution pipelines in addition with
having multiple cores of execution. A thread of execution is a common term
that refers to a stream of machine instructions related to a single process or
program that is being executed. If a single process has two or more streams
of machine instructions being executed, it is called a multithreaded process.
To facilitate handling instruction executions, performing arithmetic and
logic operations on multiple values, and to temporarily store intermediate
computations – processors use registers. Registers being internal to
processors, represent the fastest memory in the complete system. Each
architecture and family of processors names its registers differently and
sometimes name them in accordance with intended purposes. Therefore,
some registers are for general purposes, and others are for special purposes.
For example, in x86 and x64 architectures common registers are:
Figure 18. x86 and x64 Registers

Here registers are of varied sizes with some of them representing lower
bytes or lower words of their corresponding larger registers. As a special
register, IP (Instruction Pointer) or its larger variants track execution of
code by holding the address of next instruction that will be executed. Other
registers are there to help with certain other tasks.
Let us say we need to add two numbers. Therefore, first we can load the
first number in one register and the second number in another register, then
both the values can be provided to internal processor circuits that are
capable of performing arithmetic operations, and the result of the operation
can be stored in another register that can later be read or transferred to a
memory location for later use.
There are other registers as well for special operations – for example,
floating-point calculations, running single instruction on multiple data
values, controlling memory operations, tracking operation states, and many
other complicated operations that newer processors offer. It is pertinent to
mention that other architectures like for example ARM names its registers
differently and exact functionality can be learnt through processor
documentation. The beauty of higher-level programming languages comes
in part that we do not have to memorize the names of these registers to
perform common operations, as compilers and execution environments take
care of these elements in the background.
Another especially important feature that processors offer is “timers,”
that enable controlling external hardware, internal code execution and
provide timings. For instance, Operating Systems use timers to allow
multiple threads to keep running one after the other – let us say we have a
processor that has 4 cores; at max it can run 4 threads of instruction streams
in parallel; but if we have more number of threads; then to let all the threads
run in their own turns, OS Kernel uses timers. For example, to let a thread
run for 30 milliseconds the OS Kernel can start a timer for 30 milliseconds
and let the thread execute on the processor; now when the specified time
elapses an interrupt is generated internally that lets the Kernel code run that
in turn switches execution context to let new thread run during next
interval. For real-time applications, timers are specifically important.
ROM
Read-only Memory – as the name indicates is memory that can only be
read, not written. Although it is an oversimplification nowadays, due to this
name having its roots back in the early days of computing. Nowadays,
mostly any memory regarded as read-only, can actually be written
electronically. For example, BIOS (Basic Input Output System) or their
newer version UEFI (Unified Extensible Firmware Interface) can be
regarded as ROM in desktops, yet they can be upgraded. In a general-
purpose computer, ROMs are primarily used for initial system startup and
for microcontrollers these are for storing main program that executes.
Though, there are read-only memories in use that cannot be written at all.
As another important feature, data written is not lost when power is lost
or interrupted – it is particularly important for microcontrollers to store
settings and other user provided data in EEPROM (Electrically Erasable
and Programmable Read Only Memory) or Flash memories.
RAM
Random Access Memory is where most of the transactions are done by
the processor – all the variable data is stored here. Especially, in general-
purpose computers, any program that needs to be executed is first loaded in
RAM and then execution is done from there.
Mostly, RAMs are byte-oriented devices – meaning that each byte has a
unique address, and the minimum width of data that can be read or written
is a byte (8-bits) or multiple bytes depending upon number of data lines
between processor and RAM. An important thing here depends on how
values are stored in the RAM addresses. For example, if a number that
needs 8-bytes to represent has to be stored in consecutive memory
locations, say a hexadecimal number 1122334455667788 has to be stored in
RAM location 0x0000000000001000 on a 64-bit computer. This can be done in
one of two ways:

Figure 19. Little vs Big Endian

In little endian, least significant byte is stored first and in big endian,
most significant byte is stored first, with other bytes stored in consecutive
locations. For example, Intel processors use little endian scheme, whereas
some ARM based processors and microcontrollers are capable of handling
both types of storage schemes. This becomes quite important when
managing underlying raw data is required.
Instruction and Data Caches
To state simply, cache (read as “cash”) has only one purpose to perform
– boost execution performance. Caches are mostly internal to processors;
however, they can be installed outside the processor as well. To cut a long
story short, whenever the processor reads instructions or data from RAM,
caches store them and during subsequent reads of same instructions or data
(if they are available in cache), the read times are significantly reduced as
compared with reading from actual location.
Hardware Controllers
Hardware controllers – as the name suggests, control the hardware. A
single controller can control single or multiple hardware devices; but more
importantly, the controllers provide an interface between hardware devices
and the processor. There are many types of controllers we encounter
commonly, rather without paying much attention to them: USB (Universal
Serial Bus) controllers, HID (Human Interface Device) controllers, Ethernet
/ Wi-Fi controllers and SATA (Serial ATA) controllers; just to name a few.
Direct Memory Access
Normally whenever hardware completes transmitting provided data or
receives part of data from outside, it raises interrupt to tell the processor of
the event so that the processor can use the received data or can provide next
data for transmission. For example, if serial communication is being done,
after receiving each byte or transmitting each byte, interrupt can be raised
so that next actions can be taken.
Servicing interrupts is a heavy task – as the processor needs to stop
doing whatever it is doing and service the pending awaiting interrupt. When
communication rates are high, or multiple hardware are generating
interrupts, it can reduce processing efficiency a lot as considerable time
would be required in servicing the interrupts instead of running the
application. This issue is resolved by using Direct Memory Access,
whereby the hardware is granted access to memory locations.
During a data transmission operation, hardware can transmit the data
consecutively written in memory locations; and at the end, upon operation
completion, it finally raises interrupt. Whereby during a data reception
operation, hardware can store the received data in consecutive memory
locations; and at the end of the operation, it can finally raise interrupt for
processor to finally handle the data. This drastically reduces the number of
interrupts raised, and makes the interrupts more meaningful – thus,
allowing better utilization of processing resources.
Comments
Before we begin discussing the coding aspects, it is quite important to
consider commenting as well. We know that C++ code files are basically
plain text files that are parsed by compiler to produce machine code. These
files can contain comments that are skipped by compiler while parsing the
file; hence, comments are useful for documenting the code parts within the
code files.
Two types of comments are supported: Single-line comments start with
two forward slashes // and end at the end of corresponding text lines;
anything before // remains part of code, like:

…some code… // This is comment text describing code

// This complete line is comment

// This text will also be skipped by the compiler just like…

// the other comments.

The second one supported is multi-line comments that can span multiple
lines as the name suggests. A multi-line comment starts with /* and ends
with */ and anything before the starting sequence and after the ending
sequence remains part of code. These comments are
non-hierarchical – that whenever a multi-line comment is started with /*
and ended with */ it cannot contain internal comments with /*…*/ as the
first time */ is encountered after starting the comment will end the multi-
line comment. For example:
…some code… /* Anything that comes after the beginning of comment till

the ending of comment is simply skipped by the compiler.

So, we can easily document code pieces just to remember when we see those pieces maybe
after many years or so… */

Commenting is an excellent tool to help in developing any application,


while practically helping in documenting a developer’s thinking and later
understanding the code during maintenance, upgrades, and bug-fixing.
There is a lot of guidance available online about how to write better
comments; some people even suggest of writing comments and describing
each line of code – that is an overkill, as coders already know the syntax of
programming language and can read what the code line is doing. However,
a lot of useless comments can in fact create clutter and slow the pace of
understanding the code – the best comments describe “why and how”
instead of “what” – describing why and how something is being done is
much better than describing what is being done in most of the cases.
Constants and variables
At the most basic level, digital computing devices are simply number
crunching and number transforming devices – as numbers can represent
things, like, alphabets, colors, shapes, personal information, physical
properties, etc., in this regard, in a program or software application, a
variable is the most basic element that is a named container to hold a
number. And when the number is not intended to change throughout the
execution of a program, it is referred to as a constant – like, the value of PI
(3.14159…), once defined in a program must remain the same throughout
the execution.
C++ uses following format to define a variable:

<Data Type> <Variable Name>;

Or to define a variable by giving it an initial value:

<Data Type> <Variable Name> = <Initial Value>;

However, when the value assigned is not intended to be changed


throughout the execution:

const <Data Type> <Constant Name> = <Permanent Value>;

Semicolon “ ; ” is a statement terminator used by most of the C language


style-descendants – like, C++, C#, and Java.
Each datatype has an associated size – thus a variable or a constant can
hold a number or value within a certain range that depends upon the
datatype. To store and use different ranges of values having distinct types of
numbers, different datatypes are provided as defaults:
Type of value Datatype Size in bytes
Integers or char 1
whole numbers short 2
(Like 1, 2, 3…) int 4 Machine dependent
long 4
long long 8
Floating-point float 4 Machine dependent
numbers
(Like 3.14159…) double 8

Both positive and negative numbers are supported by default, as each


integral number’s datatype( char , short , int …) represents a signed number
i.e., supporting both positive and negative numbers by having the most
significant bit as a sign-bit. When the most significant bit is set to 1 , it
means that the number stored is a negative number in two’s complement
form.
For taking 2’s complement of a number, let us say decimal number 5
with 8-bit binary representation: 00000101 . Its 2’s complement is taken by
first flipping 1 s to 0 s and 0 s to 1 s i.e., taking 1’s complement that
makes binary: 11111010 . In the end, adding 1 to it, that makes binary
representation: 11111011 ; the final binary represents the negative number
in 2’s complement form; in this case it is -5.
When only positive numbers are intended to be used, unsigned is added
before the datatype.
Like:

char a;

char b = 5;

char c = -3;
unsigned char d = 128;

Here in this example, we have defined four variables named a , b , c and


d – a variable name should start with a letter or an underscore, and can
contain letters, numbers and underscores: like, my_variable_123 . All our
variables in this example are of a single byte in size; the first three can hold
both positive and negative numbers, whereas the last variable can only hold
positive numbers – all within a single byte. As in the case of variables a , b
and c , the most significant bit represents the sign; thus, leaving only 7 bits
to represent number; therefore, these can store any value from -128 to +127.

bit bit bit bit bit bit bit bit


8 7 6 5 4 3 2 1
sig
number
n

However, in case of variable d , we can store any number from 0 to 255,


as the most significant bit is not reserved for sign.

bit bit bit bit bit bit bit bit


8 7 6 5 4 3 2 1
number

Whereas a constant can be defined like:

const float PI = 3.14159f;


Value notations
Values assigned to variables or constants can be written in different
notations, commonly used ones are:
1. Decimal values are written as normal numbers.

int port_number = 5;

2. Numbers in octal representation can be prefixed with 0.

int octal_number = 0123;

3. Values represented in hexadecimal notations are preceded with


0x .

const long PORT_B_BASE = 0x0000FE00;

4. When a floating-point value is written as it is, it is treated as a


double unless it is suffixed by f to represent a float .

const double PI = 3.14159;

const float QS = -6.667f;

Exponential notations are also supported, for example, 3.0x108 can be


written as:
const double SPEED_OF_LIGHT = 3.0e8;

5. ASCII characters can be enclosed within( ‘’ ) single quotes that


way they represent corresponding ASCII code of the character
enclosed between single quotes:
const char SPACE_CHAR = ' ';

const char CAP_A_CHAR = 'A';

Here SPACE_CHAR gets the value 32 as 32 is the ASCII code for space
character, whereas CAP_A_CHAR gets the value 65 .
6. Since C++14, direct binary number representation has been
supported by prefixing the binary number with 0b :

const unsigned char bit_mask = 0b00001111;


Scopes
Scopes in C/C++ can be defined as the areas of availability of any
symbol in the application code – in other words, all the places from where a
defined symbol can be accessed, like for example, a variable or a constant
can only be accessed after it has been defined. When any variable is not in
accessible scope, compiler errors out and complains that the symbol is not
defined – as it is inaccessible at that point in code. As compilation of a
compilation unit (“ .cpp ” or any code file) takes place from top to bottom,
therefore, variables must always be first declared or defined and then they
can be used in later part of code within their scopes.
Scopes, at the very basic level are hierarchical and separated by pairs of
braces {} and their internal braces as well – anything that does not come
within any set of braces is called globally scoped.

// Accessible are:

int variable_1 = 1;

// Accessible are: variable_1

// Accessible are: variable_1

int variable_2 = 1;
// Accessible are: variable_1, variable_2

// Accessible are: variable_1, variable_2

int variable_3 = 1;

// Accessible are: variable_1, variable_2, variable_3

// Accessible are: variable_1, variable_2

// Accessible are: variable_1

Like, our sample application starts from main() and the braces after it
define its scope. Anything that is defined outside its scope is global in scope
and anything inside the scope is in local scope. The outer scope variables
can be accessed from inner scope just by using their names; however, when
the same name is used for an inner-scoped variable, it hides the outer
scoped variable within the inner scope. In such a case, to access a globally
scoped variable with same name, two colons “ :: ” (scope resolution
operator) can be used before its name, like:
::my_global_variable = 50;

By default, a variable or a constant in global scope is accessible from


other compilation units of the application. To limit it to current compilation
unit, static keyword can be used, like:

static int global_variable = 1;

The keyword “ static ” is quite a versatile keyword in C & C++ - in this


example snippet, the defined global variable remains scoped within the
current compilation unit. This idea can be extended to functions
(discussed later) to scope them inside the current compilation unit
therefore, anything that remains inside the current compilation cannot be
accessed from any other code in another “ .cpp ” file; thus, global names
can stay clean.
Type casting
Datatypes can be regarded as buckets to hold data, with smaller buckets
capable of holding little information and larger ones capable of holding
larger information. However, when assignments between variables having
different datatypes is concerned, typecasting is required:

int my_variable = (int) SPACE_CHAR;

Here, (target datatype) is the type casting operation that allows assigning
the value on right hand side having another datatype to the variable on left
hand side having target datatype. When the target datatype is large enough
to hold complete data, it is transferred as its original value, otherwise the
data is truncated from top, hence bits are lost.
Consider variables as buckets – when a value is cast into a larger bucket
it fits in completely, whereas, when larger bucket is cast into a smaller
bucket, it can lose data.
Loss of data during casting operation happens on most significant bytes;
however, the data in least significant bytes is copied to target variable.
Value vs Reference types
C/C++ allows two types of variables when referring to data – ones that
actually hold data values are referred to as value types and the ones that
hold reference to other variables’ data are referred to as reference types.
Essentially, without making copies of same variables, reference types can
refer to same data but with different names. A reference type can be
denoted by an ampersand “ & ” before their name:

int my_variable = 5;

int& reference_to_my_variable = my_variable;


reference_to_my_variable = 45;

Any updates made through reference type are actual changes to


referenced data.
A reference is just another name for the same variable storage location.
Operators
Operations on variables are fundamental to any programming task – like
adding two numbers stored in two variables and storing the result in a third
variable; thus, operators represent fundamental building-block of
performing operations.
Assignments
The simplest assignment operator is = that assigns the evaluated result
of right side to the variable given on the left side of assignment operator:

int value_1 = 10;

int value_2 = 20;

The two statements assign values 10 and 20 respectively to value_1 and


value_2 . In addition, multiple variables can also be assigned with the same
value:

value_1 = value_2 = 100;

We can read this statement from right to left as that is what an


assignment operator does: value 100 is assigned to the variable named
value_2 and then to value_1 , as their right-hand expressions evaluate to 100 .

Mathematical operations
Operato
Purpose Example Result
r
Resultant value after adding A
+ Addition A + B
and B
Gives result by subtracting B
- Subtraction A - B
from A
* Multiplication A * B Gives resultant value of
multiplying A with B
Quotient of integers’ division
/ Division A / B or full division result in
floating-point divisions
Remainder of integers’
% Remainder A % B
division

Here the + , - and * operators work according to common


mathematical intuition for addition, subtraction, and multiplication. The
division and remainder operators work differently on integers and floating-
point numbers. When floating point numbers are used or at least one
floating-point number is used in division operation, it provides full division
result in floating-point format. However, when two integers are divided –
like for example 5 / 2 gives 2 as a result and 5 % 2 gives the remainder 1 as
a result.
Another set of special mathematical operators includes ++ (increment)
and -- (decrement) operators, which are special in the sense that they only
operate on integral values to increment or decrement by one and can
produce different results depending upon whether either is used as a
prefix( ++variable_name or --variable_name ) or as a postfix( variable_name++ or
variable_name-- ). For example:

int variable_1 = 5;

int a = variable_1++;

int b = ++variable_1;

Here, postfixed increment of variable_1 is used in assignment of variable


a , and prefixed increment in assignment of b . Hence, variable a gets
assigned with value 5 as the current value of variable_1 is used first to
evaluate the value for a , and variable_1 is incremented afterwards to value
6 . Whereas, for evaluating b , variable_1 is first incremented, which
currently holds value 6 , it is incremented to 7 and then 7 is assigned to
variable b . Same logic applies in case of -- operator.
Another form combines mathematical operations with assignment, like:

value_1 = value_1 + 10;

In this example, 10 is added to the currently held value of value_1 and


resultant value is stored back to value_1 itself, thereby increasing its held
value by 10 . It can be written with the shorter form:

value_1 += 10;
Similarly, there are -= , *= , /= and %= operators:

Operator Example Evaluates to


+= A += B A = A +B
-= A -= B A = A -B
*= A *= B A = A *B
/= A /= B A = A /B
%= A %= B A = A %B

Boolean logic
In C/C++, anything that evaluates to 0 is considered Boolean false, and
anything other than that is considered Boolean true. This often confuses
new developers and can cause many bugs in applications – for example, a =
5 is a Boolean true in C++, no matter what the older value was held in
variable a , it gets updated to 5 and the whole expression evaluates to true,
as it evaluates to a non-zero value. Therefore, to evaluate Boolean logic,
special attention must be given to avoid inadvertent use of equality operator
whereas the actual intention is to evaluate Boolean logic with equality
check ( == ) operator. Available Boolean operators are:

Operator Stands for Example Evaluates to true when


< Less than A<B A is less than B
> Greater than A>B A is greater than B
Less than or A is less than B or it is
<= A <= B
equals equal to B
Greater than or A is greater than B or it is
>= A >= B
equals equal to B
== Is equal A == B A and B are equal
!= Is not equal A != B A and B are not equal
Boolean Combinations
A and B both evaluate to
&& Boolean AND A && B
true
|| Boolean OR A || B A or B, or both evaluate to
true
Boolean negation
A evaluates as Boolean
! Boolean NOT !A
false

Combinations of Boolean logic can be done using parentheses like (A >


B) && (C < D) evaluates to Boolean true only when both the parentheses
evaluate to true.
Boolean Bit-wise operations
In C/C++, binary operations are intended to allow bit-wise operations
between two values – let us say, we have a value in a variable that we need
to extract lower nibble (4 bits) from and place the resultant nibble in the
upper nibble of another variable, this requires using binary bit-wise
operations:

Operato
Stands for Example Returned resultant value
r
<< Left shift A << B Bits in A are B times shifted left
>> Right shift A >> B Bits in A are B times shifted right
&
Bitwise A&B
Bits in A AND with corresponding
AND bits of B
Bitwise Bits in A OR with corresponding
| A|B
OR bits of B
Bitwise Bits in A XOR with corresponding
^ A^B
XOR bits of B
Bitwise Bits in A are inverted ( 1 s to 0 s and
~ ~A
NOT 0 s to 1 s)

Additionally, these operations can be combined with equality operations


having shorter forms:

Operator Example Evaluates to


<<= A <<= B A = A << B
>>= A >>= B A = A >> B
&= A &= B A = A &B
|= A |= B A = A |B
^= A ^= B A = A ^B

Ternary operator
Ternary operator decides between two options depending upon a
Boolean condition:
boolean_condition ? expression_1 : expression_2

When the boolean_condition evaluates to Boolean true, expression_1


becomes the result otherwise expression_2 becomes the result of ternary
operation. For example:

int result = my_variable > 5 ? 20 : 5;

In this example, if value of my_variable is greater than 5 (making Boolean


expression true), result is set to 20 otherwise result is set to 5 .
Operators’ precedence
In mathematical operations division and multiplication take precedence
over addition and subtraction i.e., division and multiplication are done
before addition and subtraction in
a + b * c - d / c . To simplify, or when in confusion, use brackets () as they have
greatest precedence over other evaluations. Therefore, we can easily get an
expression evaluated before the others – nested use of () evaluates from
inside outwards, like:

result = ((value1 + value2) * value3) – value4;

The expression inside the inner-most pair of parentheses i.e., (value1 +


value2) is evaluated first, then the resultant value is multiplied with value3 to
compute the resultant of outer pair of parentheses, and lastly value4 is
subtracted from the resultant to produce the result of the whole expression
that is then assigned to result .
Arrays
Real power of programming comes from working on larger datasets;
and to represent larger, and same type of data, arrays are used as a
fundamental element:

int my_data_elements[5];

This defines an array named my_data_elements of 5 integers, which are


placed one after the other. An array can be defined and assigned with
individual values at the same time:

int my_data_elements[5] = { 10, 20, 30, 40, 50 };

In memory, it looks as if 5 integers are placed consecutively, one after


the other, and each individual element can be accessed, read, or updated
through its index:
Index 0 Index 1 Index 2 Index 3 Index 4
10 20 30 40 50

To access the third element that currently holds 30 , we can use its index
value, which is 2 as the indices start with 0 . Therefore, the following code
updates its value to 333 .

my_data_elements[2] = 333;

This only updates the individual element, nothing else – the array now
looks like:

Index 0 Index 1 Index 2 Index 3 Index 4


10 20 333 40 50
We can easily extend their use by having 2 or many dimensional arrays
– for example:

int my_2d_array[2][3] = { { 10, 20, 30 }, { 11, 22, 33 } };

The first dimension can be regarded as rows and the second as columns
– with this array having 2 rows and 3 columns:

Column 0 Column 1 Column 2


Row 0 10 20 30
Row 1 11 22 33

To access individual element, we can use similar notation as used


earlier:

my_2d_array[1][1] = 100;

As the array’s indices are 0-based, this statement updates the second
element in second array – or we can say, the element in second row and
second column is updated, thus making it:

Column 0 Column 1 Column 2


Row 0 10 20 30
Row 1 11 100 33

This 2-dimensional representation is only for understanding purposes,


whereas, in the background it creates an array of arrays – placing single
dimensional arrays in memory one after the other.
A special type of array is a string of characters, wherein, a sequence of
ASCII characters is placed one after the other and terminated with zero – a
null terminator:
char message_1[6] = { ‘H’, ‘e’, ‘l’, ‘l’, ‘o’, 0 };

char message_2[6] = { ‘W’, ‘o’, ‘r’, ‘l’, ‘d’, 0 };

Later, we will see better ways of storing messages in the form of strings.

Standard I/O
As we have established till now that any software or firmware that we
develop can be simply stated to be taking some inputs, processing them and
producing outputs accordingly. Additionally, in this book we are looking at
C++ with the purview of applying the knowledge in developing
applications for Arduino and other microcontrollers in general. In the small
devices’ world, inputs and outputs depend on hardware devices being
connected with microcontroller. Therefore, for keeping our learning focused
on purpose, we will use standard console inputs and outputs provided by
standard C++ library iostream ; Therefore, for outputting text on screen we
shall use std::cout (console output) given in standard library, like:

#include <iostream>

int main() {

std::cout << "My Text ..." << std::endl;

return 0;

}
For taking input from user, we shall use std::cin (console input), also
given by standard library. For example:

#include <iostream>

int main() {

int val;

std::cout << "Enter value: ";

std::cin >> val;

std::cout << "You typed " << val << std::endl;

return 0;

In both the examples, the first line “ #include <iostream> ” in each tells the
compiler that we need to use some functionality provided by the standard
library named iostream , and the functionality that we need for outputting text
on console is through using std::cout , and for taking input from console (by
typing from keyboard) is through using std::cin , whereas, std::endl represents
the end of line or the newline character.
In using std::cout , we can send multiple values of variables, constants or
text enclosed between quotation marks to output screen by separating them
with << operator. In the first example, text enclosed between quotation
marks followed by an end of line character is sent to console output;
whereas, in second example, std::cout is used two times – first time it only
prints text on screen without outputting a newline so that user inputs text on
the same line as the message is printed; and in second use it outputs three
items i.e., text, variable value and newline. Example use of second snippet
is:

Figure 20. Compiling and running standard input example

Just note here that the user has typed 123 at the input, which is stored in
val and later displayed in output on second line.
Structures
Structures are a way to combine different types of related data into
creating new datatypes that can represent an object or an element as a
combination of multiple properties.

struct structure_name {

};

Structure naming rules are the same as for naming the variables. To
elaborate their use, let us say we need to represent many circles; we can
combine all the data that represents a circle in a structure:

struct circle {

float x_position;

float y_position;

float radius;

};

Now we can represent many circles in a simpler way:

struct circle c1, c2, c3, c4, c5;

Each one of them i.e., c1 , c2 , c3 , c4 and c5 represents an individual


circle, each having all the three variables. Individual properties of a circle
can be accessed by using “ . ” operator:

c1.x_position = 120.50f;

c1.y_position = 100.99f;

c1.radius = 10.0f;

c2.x_position = 101.00f;

c2.y_position = 110.00f;

c2.radius = 12.2f;

c3.x_position = 310.10f;

c3.y_position = 100.55f;

c3.radius = 19.50f;

c4.x_position = 180.00f;

c4.y_position = 900.45f;

c4.radius = 11.0f;

c5.x_position = 650.45f;

c5.y_position = 540.25f;
c5.radius = 20.0f;

Where f suffix tells the compiler to treat the floating-point numbers as


float values, not as double values.

In the memory, all these instances i.e., c1 , c2 , c3 , c4 and c5 of the


same structure possess all the elements that the original structure defines,
and look something like:

Figure 21. Structures depiction in the memory

Although, in C++, structures can contain other types of elements as


well; but we will not discuss them here – as of now, we will just suffice in
drawing understanding that structures are just combinations of individual
variables, rest of the things will be discussed in classes – as structures in
C++ are quite like classes.
Program execution flow control
A C++ program starts its execution from the beginning of main()
function and executes till the end – by executing one statement after the
other.

The main() function itself is executed by the background code that sets
up the execution environment for the program to run and passes in the
command-line arguments into the main() function; though, that is not
part of our discussion here – here we will only assume that main() is the
function where the execution begins.

Here, to alter the straight path of execution, C++ provides various


statements that we can use to apply logical detours and repetitions in our
programs – and it is these basic elements that add sense to programming by
controlling the execution flow.
Conditional statements – if, else if, else
In the simplest form, if protects a statement or a block of statements
from executing unless a Boolean condition evaluates to true:

if (condition)

…a single statement…;

By default, if only lets the following statement execute when the


condition is true; to apply conditional execution to a block of code, we can
enclose it within curly braces:

if (condition) {

…statements…

In this way, the whole block of code, i.e., enclosed between the braces
will either execute fully or will not execute at all, depending upon whether
the condition evaluates as Boolean true or false. When the condition is true,
the whole block is executed otherwise the whole block of code is skipped.
Just to reemphasize, when braces are not used, if only applies conditional
execution to its following single statement, so, whenever multiple
statements are relevant in execution context, always use braces to enclose
them into a single block of execution.

expands upon this behavior – whereby, a block of code or a single


if...else
statement written after if is executed when the condition is true, otherwise,
a block of code or a single statement written after else is executed. This
behavior provides mutual exclusion amongst the two statements or the two
blocks of code – only one of them runs depending upon condition:
if (condition) {

…block 1 …

} else {

…block 2 …

}
Here, …block 1… is executed when the condition is true, otherwise …block
2… is executed – not both.

This idea is further enhanced by having one or many else if (condition)


between if and else , to provide mutual exclusion amongst three or more
number of statements or blocks of code:

if (condition-1) {

…block 1 …

} else if (condition-2) {

…block 2 …
} else if (condition-3) {

…block 3 …

} else if (condition-4) {

…block 4 …

} else {
…block 5 …

This way, we can have any number of else if conditional checks as we


need and can even skip the last else altogether to let only one block run in
exclusion when corresponding condition evaluates to true. In this example
snippet, condition-1 is evaluated first, if it evaluates to Boolean true, …block
1… is executed and all the rest of the conditional checks and blocks are
skipped; however, when condition-1 evaluates to false, condition-2 is
evaluated, if it evaluates to true, …block 2… is executed and rest of the
conditional checks and blocks are skipped from evaluation and execution.
This evaluation goes on in sequence, and when no condition evaluates to
true, …block 5… that is controlled by else is executed. Let us write a small
piece of code to demonstrate this concept:
#include <iostream>

int main() {

int age;

std::cout << "What is your age? ";

std::cin >> age;

if (age < 0) {

std::cout << "Age cannot be negative." << std::endl;

} else if (age == 0) {

std::cout << "Are you just born?" << std::endl;

} else if (age <= 10) {

std::cout << "Let us play" << std::endl;


} else if (age <= 20) {

std::cout << "Let us study" << std::endl;

} else {

std::cout << "Enjoy!" << std::endl;

return 0;

The sample output is:


Figure 22. Compilation and execution of if...else example

We can clearly see in the example output that during each execution,
input is taken from the user, and evaluation is done in a chain of conditional
checks, whichever evaluates to true lets the controlled block of code run
and skips the rest.
Conditional statements – switch
if…else evaluates conditions and runs the corresponding statement or a
block of statements accordingly. However, when a single expression is
compared against a set of constant values to decide which set of statements
to run accordingly, switch provides a cleaner way with following format:

switch (expression) {

case constant-1:

…statements block 1 …

break;

case constant-2:

…statements block 2 …

break;
case constant-3:

…statements block 3 …

break;

case constant-4:

…statements block 4 …

break;

default:

…statements block 5 …
break;

Here the expression is compared against constant-1 , constant-2 , constant-3 ,


and constant-4 if it equals any of these, the corresponding …statements block…
starts execution till encountering break , which then exits the switch block. If
expression does not match any of the constants, default gets executed. For
example, if expression equals
constant-1 , …statements block 1… starts executing and upon encountering break
statement, switch block is exited; but if there is no break statement,
execution continues onto the next statements under the next cases till the
time break is encountered or the switch block ends.
To illustrate, let us consider the following example:

#include <iostream>

int main() {

int option;

std::cout << "What do you want to see?" << std::endl;

std::cout << " 1 = Binaries" << std::endl;


std::cout << " 2 = Logging" << std::endl;

std::cout << " 3 = Data" << std::endl;

std::cout << " 4 = Support" << std::endl;

std::cout << "Give me a number: ";

std::cin >> option;

switch (option) {

case 1:

std::cout << "Displaying Binaries" << std::endl;

break;

case 2:

std::cout << "Displaying Logs" << std::endl;

break;
case 3:

std::cout << "Displaying Data" << std::endl;

break;

case 4:

std::cout << "Displaying Support Info" << std::endl;

break;

default:

std::cout << "Invalid option" << std::endl;

break;

return 0;

Its compilation and sample output:


Figure 23. Compilation and running the switch example

The example is quite simple, as the input value is compared against a set
of constants that are expected, otherwise the option is expected to be
invalid.
Looping – for loop
Repeating a certain single statement or a block of statements is simply
referred to as a loop. In C++, three types of loops are provided.
We also have goto statement that unconditionally jumps to another part
of code denoted by tag (like, jump_here: ) and program starts executing
from there; in combination with conditional checking e.g., with if…else ,
goto can be used for repetitions – though, usage of goto is considered by
many as a bad practice, as one can easily go offtrack and software bugs
can be quite hard to locate.
Each of the three loops we are going to discuss, either repeats a single
statement or multiple statements enclosed within curly braces {} – just like
we have seen in if…else usage.
The first and probably the most elaborate loop is for – in recent
standards, for loop has been enhanced to work on collections and arrays;
however, we will only be using its original and older form:

for (initialization; condition; update) {

…block of code…

When for loop is executed: first, initialization statement is executed – it is


executed only the first time the loop is entered; after initialization , condition is
checked – if the condition evaluates to Boolean true, loop body i.e., the …
block… is executed; after execution of loop body, update expression is
evaluated; then condition is checked again, and body is again executed if
condition still evaluates to Boolean logic true. This cycle repeats until the
condition evaluates to Boolean false – in that case the loop exits and stops
executing the …block… .
An especially important thing is to not add a semicolon at the end of for
loop like:

for (initialization; condition; update); {

…block of code…

It is NOT RIGHT – as a semicolon itself is a blank statement – this


would cause to loop over a blank statement – the semicolon.
Let us apply for loop to print a table by taking input from user:

#include <iostream>

int main() {
int table;

std::cout << "Table of what? ";

std::cin >> table;

for (int n = 1; n <= 10; n++) {

std::cout << table

<< " x "

<< n

<< " = "

<< table * n

<< std::endl;

return 0;

}
A sample execution is:

Figure 24. Compilation and execution of table generating example

We are simply taking in input from user and running the for loop to
have values from 1 to 10 – incrementing the variable after each iteration of
the loop; therefore, simple screen output is generated during each loop
iteration.
Looping – while loop
The simplest loop is while – simply speaking, while keeps running a
block of code or a single statement as long as the condition remains
Boolean true:

while (condition) {

…block…

When the loop starts, first the condition is evaluated, if it evaluates to


Boolean true the loop body is executed; then condition is checked again, if it
is still Boolean true, the body is executed again – this keeps repeating until
the condition goes false, otherwise it runs infinitely. Therefore, the loop body
must include any statements that would cause the condition to go false to exit
the loop. Therefore, controlling the loop is up to the developer.

It is important to keep in mind that semicolon is not placed just after the
while condition parentheses – as semicolon itself is a blank statement;
therefore, once the loop starts executing, it may run forever.

while (condition); {
…block…

This is WRONG – semicolon does not let the while loop repeatedly
execute …block… as the while loop executes its following single
instruction unless it has a block of code within a set of braces {} ; since a
semicolon is a blank instruction itself just after the while statement, this
is what is executed by the while loop in this case not the instructions
given inside the block of code.
For understanding purpose, let us implement the same table application
with while loop as well:

#include <iostream>

int main() {

int table;

std::cout << "Table of what? ";

std::cin >> table;

int n = 1;

while (n <= 10) {

std::cout << table

<< " x "

<< n

<< " = "


<< table * n

<< std::endl;

n++;

return 0;

The sample output:

Figure 25. Compiling and executing while loop example

The initialization of variable is done outside the loop here, and the
control variable is incremented to control the loop – this ends the loop when
table is complete.
Looping – do-while loop
When we have a requirement to execute a certain block of code at least
once, and its repetition must be decided after first execution, we can use
do…while loop:

do {

…block…

} while (condition);

In this loop, the …block… is executed then condition is evaluated, if the


condition evaluates to Boolean true, the …block… is repeated, and …block… is
kept repeating until condition turns false.
As an example:

#include <iostream>

int main() {

int value;

std::cout << "Enter 5 to exit: " << std::endl;

do {
std::cout << " Value?: ";

std::cin >> value;

} while (value != 5);

return 0;

The sample output:

Figure 26. Compiling and running do...while example

We are simply taking user input and evaluating it to keep the loop
running – as we need to get the first input in any case, we are using the do…
while loop for this scenario.

Looping – break and continue


The last thing in controlling the loops is about break and continue
statements:

break;

continue;
Whenever a break statement is executed within the body of a loop, the
loop is exited; whereas, on encountering the continue statement, remaining
instructions inside the loop body are skipped and the next loop iteration is
started.
For example:

for (int n = 1; n <= 10; n++) {

if (n == 8) break;

if (n == 3) continue;

std::cout << n << std::endl;

In this example, when the value of n equals 8 , the loop is exited as


break is encountered; and when the value of n equals 3 , text output on the
console is skipped as continue is encountered that starts the next iteration of
the loop and skips the remaining instructions in current iteration.
Blocks’ nesting
Till this point we have seen the individual working of coding constructs,
all these control flow blocks, or elements can be nested inside each other to
produce complex logic wherein each block works as an individual entity as
per logic within the whole complex working. For example, if block can
have other if blocks, and the internal ones can have other if blocks or
loops; and loops can have internal nested loops as well.
To illustrate this, let us create an application that would take a number
as an input from user and prints its table, and keeps taking inputs and
printing tables as long as zero or a negative value is not given by the user:

#include <iostream>

int main() {

int table;

while (1) {

std::cout << "Table of what? (0 or negative to exit) ";

std::cin >> table;


if (table <= 0) break;

for (int n = 1; n <= 10; n++) {

std::cout << table

<< " x "

<< n

<< " = "

<< table * n

<< std::endl;

return 0;

}
Here the outer loop while (1) is an infinite loop because in C/C++ any
non-zero value evaluates to Boolean true, therefore this outer loop never
exists on its own. Within the loop we are getting input from user, and if the
provided number does not meet our valid range of values, we break out of
the main loop, otherwise the table is printed.
The sample execution goes like:

Figure 27. Compiling and executing the tables example

This way of having nested blocks can go any number deep; though,
management of deep level loops or conditional checks can be tricky if code
is not managed cleanly.
Challenge
As a practice exercise, try to produce following output by using nested
loops only:

***

*****

*******

*********

***********

*************

***************

*****************

***************

*************

***********

*********

*******

*****

***
*

Just remember while doing it that there is no single right solution,


everyone’s solution can be different – what matters is the result.
intentionally left blank
Chapter 4:
Revisiting C++ — Functions, Classes,
Namespaces, and Pointers

H ave you ever heard of terms like Technical Debt and Big Ball of Mud? If
you are familiar with such terms, you can well understand the
importance of the topics we are going to cover in this chapter. For those of
us who are unfamiliar with these: these terms refer to a lot of code that has
been built over an extended period, maybe years, spending a lot of money,
but the code is essentially painful to handle – its management, bug fixing,
reusing in other projects etc., are more painful than pleasure.
You can write a whole application by using simple variables, operators,
conditional blocks, and loops only – however, ease of code management,
bug-fixes and features’ extensions can equally become increasingly
complicated as the size of source code grows; and any additions or
alterations in one part of code can cause side-effects in other non-related
parts. To better manage code, it should be easily readable by humans and
well organized, and to remain easily portable to other hardware or software
stacks, its majority should not be tightly dependent upon underlying
hardware or software stack. Therefore, this chapter can be summarily said
to be related with language provided facilities for better code management,
reusability, and organization.
Functions
Functions are the fundamental tools for a piece of code reusability
within an application.
Wherein, a function in C++ can optionally take inputs, and can optionally
return a value – functions are just like math functions:

This sample math function illustrates the idea that by changing the input
values x and z, the assigned value in y is changed whereas the procedure to
compute the value remains the same.
In C++, function takes following general form:

<return-type> function_name(

input-type-1 input_1,

input-type-2 input_2,

input-type-3 input_3) {

…function-body…

return some_value;

}
Rules for naming a function are just like naming the variables and
constants i.e., it can only start with a letter or an underscore and can only
contain letters, underscores, and numbers.
For invoking or calling a function, it can be done by using its name and
passing in the input parameters as required:

int my_variable_1 = my_function(1, 2, 3);

int my_variable_2 = my_function(4, 5, 6);

In this snippet, two integers i.e., my_variable_1 and my_variable_2 are


declared and assigned with values that are returned by two invocations of
my_function by passing in different input parameters during each invocation.
The returned value can either be assigned to a variable as we have done
here, or used in calculation, or can even be fully ignored – it is up to the
required use-case scenario, e.g.,

int my_variable = my_function(1, 2, 3) * my_function(3, 3, 5);

To elaborate on the concept of returning a value – a function can have


one or many return statements depending upon situation – the corresponding
returned value is the one that is assigned to a variable using assignment
operator, like, for example:

int absolute_value (int input) {

if (input >= 0) return input;

else return -1 * input;


}

Now, whenever positive value is given as input to the function, the first
return statement is executed to return the value as it is, and when a negative
value is given as parameter the value is sign inverted and returned, thus:

int my_variable_1 = absolute_value(5);

int my_variable_2 = absolute_value(-5);

Both the variables get the same value i.e., 5 . On executing the return
statement, further execution inside the function body is terminated and the
control is returned from where the function was invoked.
Since C++11, auto has been allowed to be used as a return datatype – it
instructs the compiler to infer the return datatype from the return statement
inside function’s body. Additionally, since C++14, decltype(auto) has also
been allowed – also for deducing the datatype of returned value but by
preserving the constant or reference nature of value. However, to remain
restricted to our purpose, let us forego these topics for the time being, as
many of our development environments in microcontroller worlds with very
low-powered devices do not fully support these features.
While working with codebases having both C and C++ source code
files, additional linkage specific prefixed extern keyword is commonly used
in the form, like:
extern "C" int absolute_value (int input) {

if (input >= 0) return input;

else return -1 * input;

It tells the compiler that the function is required to be usable in C


language; therefore, linkage specifications are honored by the compiler –
other linkage specifications are also available, however, linkage with C
language is quite common.
Recursive functions
Functions can be chained together, whereby, one function can call many
other functions, and others can call yet other functions, thus allowing
creating complex logic – each time a function executes, the flow of
execution resumes from where it was suspended during calling the function.
This behavior can be used in calling a function from within its own body –
calling or invoking a function within its own body is called recursive call.
Though it is possible, it can easily crash an application if invocation
goes many levels deep (depending upon implementation architecture), thus
causing a heavy toll on memory stack. Therefore, there must be some
conditional checks within the body of the function that stop recursive calls
at some point. Following example computes factorial of a given number
through recursive calls:

#include <iostream>

int factorial(int number) {

if (number <= 1) return 1;

return number * factorial(number - 1);

}
int main() {

int value;

while (1) {

std::cout << "Factorial of? (0 or negative to exit) ";

std::cin >> value;

if (value <= 0) break;

std::cout << " " << value << "! = "

<< factorial(value) << std::endl;

}
return 0;

}
The main loop is quite self-explanatory – it keeps taking inputs from
user as long as zero or negative value is not provided, and for each value, its
factorial is computed and displayed. The real magic happens in recursive
call – although, its usability is limited as it can crash the application when
given number is large enough.
Its sample working is as displayed:

Figure 28. Compilation and execution of recursive factorial function example

This sample is only for demonstrating the recursive functionality; for


actually computing factorials, you should use loops – as the number of
recursions increase, memory use becomes inefficient, thus it can crash the
application when large enough number is inputted by the user.
Function variables
A function can have any number of variables, and they are scoped
within the function body. An important thing to note is that, anytime
function returns or completes execution, all of its variables are gone. Once
the function is invoked again, it gets its variables afresh unless any
variables are marked with keyword static , like:

static int consistent_variable = 1;

In such case, the variable holds its content between multiple invocations
of the function, and its initialization value is assigned only the first-time
function is invoked.
Functions with default parameters
A function can have one or more of its input parameters take default
values when no values are provided against these parameters while
invoking the function. This feature proves powerful in simplifying function
calls without specifying all the parameters.
The rule to remember is to start making parameters default from the last
parameter, as in the following example last two parameters are made to
have default values:

int my_special_function(int number1,

int number2,

int number3 = 5,

int number4 = 6) {

return number1 * number2 * number3 * number4;

Therefore, the function can now be called by providing two, three or


four arguments, like:

int value1 = my_special_function(1, 2);

int value2 = my_special_function(1, 2, 3);


int value3 = my_special_function(1, 2, 3, 4);

In the first sample use with passing in of two arguments – it sets the first
two parameters to specified values i.e., 1 and 2 respectively, whereas the
third and fourth parameters get their default values given in the function
definition. The second sample uses three arguments thereby setting the third
parameter value to override its default value; as the fourth argument is not
given, this lets the fourth parameter set to its default value. In the last
sample use case, four arguments are provided, therefore, all the parameters
will use the provided values and defaults are completely overridden.
It is clearly a beneficial feature, though the important thing to remember
here is reemphasized: the default values are given to parameters starting
from the last parameter. This can simply be said as, if one parameter is
given a default value then all the parameters coming after it must also be
given default values – if first parameter is given default value then all the
parameters should be given defaults, thereby, the function can be called
without providing any arguments at all, thereby using all the default values
of its parameters, like:
int my_special_function(int number1 = 1,

int number2 = 2,

int number3 = 3,

int number4 = 4) {

return number1 * number2 * number3 * number4;

Can be simply used as:

int special_value = my_special_function();


Functions without inputs or return value
Inputs parameters are completely optional while defining a function and
can be completely skipped in definition – therefore, when the intended
function does not need to work with supplied input arguments, blank
parentheses () can be used after the function’s name to denote that the
function does not need any inputs while calling.
Additionally, returning a value is also completely optional. Therefore,
when nothing is required to be returned from the function, void can be used
as the return datatype – this implies to also skipping the use of return
statements completely inside the function body. A function that does not
take any inputs and does not return any value can take the form like:

void function_name() {

…function-body…

Alternatively, for empty parameters, it can also be written as:

void function_name(void) {
…function-body…

Thereby, calling it as it is – without assigning or expecting any value


being returned:

function_name();

Although, such functions are intended to execute a set of instructions


given in their function bodies, thereby representing a block of code that can
be executed from anywhere in the application. However, globally available
variables and constants that are defined outside the scope of function body
are accessible and can be used, and such variables can be updated by the
function.
Just for clarity’s sake: Input parameters and return value usage are
completely up to specific requirements. There can be requirements when
one needs to use input arguments without returning any value while other
times nothing might be required as input however something might be
required to be returned – C++ is completely open to working with all such
scenarios.
Functions with variable number of arguments
C++ allows creating functions that can be invoked with variable number
of arguments in a way that total number of parameters are not defined in
function declaration. This can be achieved by using ellipsis (“…” three
dots) as the last parameter in function declaration – when a function is
declared having ellipsis as its last parameter. At least one parameter is
needed before the ellipsis in addition to some way to knowing inside the
function that how many parameters are passed in:

void my_function(int needed_parameter, ...);

This can be invoked by passing in variable number and types of


arguments against the place of ellipsis, like:

my_function(5, 20.0, 30.0f, 40, ‘A’, 70e2);

Since types are not predefined for variable list of arguments, C++ only
provides type checking for arguments provided against explicitly declared
parameters. Whereas, for variable list, datatypes of arguments are promoted
to larger ones while passing in i.e., arguments of type char are passed in by
converting to int and float by converting to double . Therefore, we can use
such functions in cases when we need greater generalization in terms of
number and types of arguments.
Standard library stdarg.h provides functionality for dealing with variable
list of arguments inside the function – including: va_start , va_end , va_list ,
va_arg and va_copy . Let us see an example to make things clear:

#include <iostream>

#include <stdarg.h>
int sum_all(int total_args, ...) {

va_list args_list;

va_start(args_list, total_args);

int sum = 0;
for(int i = 0; i < total_args; i++) {

sum += va_arg(args_list, int );

va_end(args_list);

return sum;

int main() {

int sum;
sum = sum_all(5, 1, 2, 3, 4, 5);

std::cout << "Sum = " << sum << std::endl;

sum = sum_all(8, 1, 2, 3, 4, 5, 6, 7, 8);

std::cout << "Sum = " << sum << std::endl;

return 0;

The execution output goes:

Figure 29. Compiling and running the variable arguments list example

In this example, we are passing in the number of arguments we are


using as variable list. Inside the function, we are using va_list and
initializing the list using va_start from the last argument i.e., total_args in this
case. The rest is simple looping – getting specified number of arguments as
integers using va_arg , computing their sum and returning.
Function prototyping
In C++, two terms are quite common while creating functions –
declaring and defining. Whenever any symbol is used in code, like using
constants, variables, structures, or functions etc., it must be declared
beforehand within the compilation unit otherwise the compiler would not
know what we are talking about. Therefore, declaring something is just
about telling the compiler what we are talking about – how the thing looks
line; whereas, defining something is telling the compiler about what it does
– the actual implementation. For example, our sample for factorial can be
rewritten as:

#include <iostream>

int factorial(int number);

int main() {

int value;
while (1) {

std::cout << "Factorial of? (0 or negative to exit) ";

std::cin >> value;

if (value <= 0) break;

std::cout << " " << value << "! = "

<< factorial(value) << std::endl;

return 0;

}
int factorial(int number) {

if (number <= 1) return 1;

return number * factorial(number - 1);

}
Here, the function prototype is provided before the main() function,
simply by:

int factorial(int number);

This tells the compiler that we have a function named “factorial” that
takes a single integer input and returns an integer as a result. Thereby, we
can simply use it inside the main function. Later, when the compiler reaches
the end of compilation unit, it also finds the definition of the function – the
definition can be inside another compilation unit as well as it is required
during linking process, otherwise the linker can error out saying that it
could not find the definition of something used in code.
As a note: This feature is particularly important in the sense that it helps
in creating libraries
– whereby, declarations are separated in header files so that they can be
included in multiple compilation units (“ .cpp ” or other code files) and
definitions are segregated in other compilation units or prebuilt binaries.
Function overloading
Overloading of functions refers to specifying more than one function
with same name within the same scope – thus, same named functions being
the overloads of each other. Overloaded functions allow using same name
with different semantic meanings that depend on the types and number of
input arguments. To clarify, let us see following example:

#include <iostream>

void send(int number);

void send(long long number);

void send(float number);

void send(double number);

int main() {

int intNumber = 10;

long long longlongNumber = 20;

float floatNumber = 30.f;

double doubleNumber = 40.0;


send(intNumber);

send(longlongNumber);

send(floatNumber);

send(doubleNumber);

return 0;

void send(int number) {

std::cout << "Sending INTEGER " << number << std::endl;

}
void send(long long number) {

std::cout << "Sending LONG LONG " << number << std::endl;

void send(float number) {

std::cout << "Sending FLOAT " << number << std::endl;

void send(double number) {

std::cout << "Sending DOUBLE " << number << std::endl;

}
The output shows:

Figure 30. Compiling and running the overloading example

Since we are using the same named functions for sending values,
however, the output clearly indicates that distinct functions are called
behind the scenes depending upon supplied type of argument – selected
based on function parameters.
Inline functions
Another quite common keyword inline can be used with function
definition:

inline void function_name() {

…function-body…

This tells the compiler to copy the code within the function’s body and
paste it at all the places where the function is called – although, some
compilers can opt to not fully copy the code for performance or other
reasons and keep calling the in-lined function in conventional way.

Compilers try to optimize code on various criteria; therefore, it is quite


possible that something that is assumed about the code does not hold
when final binary is generated. For example, when empty loops are
executed for certain number of times, then compiler may think that there
is nothing being done in it, therefore, the compiler may choose to
remove it as it would not affect the execution because the loop is
effectively doing nothing.
Pointers
Accessing any reachable memory location and manipulating data
accordingly is the power that pointers offer in C/C++. Simply put, pointers
are just memory addresses – that is it. Therefore, their sizes depend on the
architecture they are being used on, for example, on 32-bit systems they
have 32-bits and on 64-bit systems their size is 64-bits – their size does not
depend on the type of data they are addressing.
Pointers are denoted by an asterisk ( * ) before them while defining, let
us define few pointers to different datatypes:

int *int_pointer_1, *int_pointer_2, *int_pointer_3;

float *float_pointer_1, *float_pointer_2;

double *double_pointer;

Here we have declared three pointers that can point to integer datatype,
two are declared to hold addresses of floats and one pointer that is intended
to hold address to anything of double datatype.
Getting addresses
& Operator – in addition to providing Boolean bitwise-AND logic
operation, is also used to get memory addresses of variables and objects just
by prefixing their names. Let us say we need to assign address of an integer
to a pointer:
int my_integer = 5;

int *int_pointer = &my_integer;

std::cout << "Address of the INTEGER = " << int_pointer;

In the second line of code, & operator gets the address of variable then
it is assigned to int_pointer . This same technique can be used on other types
of data, though the type of addressed variable must match with the type of
pointer.
The same can be applied to access an array element’s address:

int my_data_elements[5] = { 10, 20, 30, 40, 50 };

int *element_pointer = &(my_data_elements[2]);

std::cout << "Address of the third element = " << element_pointer;

Similarly, objects – like, of structures, can also be addressed, let us say


we already have a structure named MyStructure :

struct MyStructure s1;

struct MyStructure *s_pointer = &s1;

std::cout << "Address of the Structure Object = " << s_pointer;

Despite pointing different elements, the size of the pointer does not
depend on the size of the pointed data – that is important.
Another way of getting addresses is during object creation using new
keyword, as new creates an object and returns its pointer:

struct MyStructure *s_pointer = new struct MyStructure;

std::cout << "Structure is allocated at address = " << s_pointer;


Let us see this in action with an example:

#include <iostream>

struct MyStructure {

int x, y, z;

};

int main() {

int int_value;

float float_value;

struct MyStructure my_structure;


int *int_ptr = &int_value;

float *float_ptr = &float_value;

struct MyStructure *struct_ptr = &my_structure;

std::cout << "INT is at " << int_ptr << std::endl;

std::cout << "FLOAT is at " << float_ptr << std::endl;

std::cout << "STRUCTURE is at " << struct_ptr << std::endl;

std::cout << std::endl;

struct MyStructure *new_structure = new struct MyStructure;

std::cout << "New STRUCTURE is at " << new_structure << std::endl;


return 0;

Its execution makes the concept clear:

Figure 31. Compiling and executing the Addresses example


Dereferencing
Dereferencing is about getting or manipulating target data addressed by
the pointer. Here we have * again but as a dereferencing operator –
prefixing a pointer with * effectively accesses the value written at the
address held by the pointer, that too as the specified datatype of pointer. Let
us make it clear through an example:

#include <iostream>

int main() {

int int_value = 10;

int *int_ptr = &int_value;

std::cout << "INT is at " << int_ptr

<< " having value " << *int_ptr

<< std::endl << std::endl;


std::cout << "Updating value through pointer" << std::endl;

*int_ptr = 20;

std::cout << "Now, pointed value = " << *int_ptr << std::endl;

std::cout << "and, variable value = " << int_value << std::endl;

return 0;

Simply prefixing a pointer with * gives access to pointed value that can
be read and updated, the output makes it further clear:

Figure 32. Compiling and executing the dereferencing example

However, the elements inside an object are accessed using “ -> ”


operator with the pointer having address to the object – thereby, allowing to
access and update members. Let us make it clear using another example:
#include <iostream>

struct MyStructure {

int x, y, z;

};

int main() {

struct MyStructure my_structure;

my_structure.x = 10;

my_structure.y = 20;

my_structure.z = 30;

std::cout << "Original structure: "

<< std::endl
<< " x = " << my_structure.x << std::endl

<< " y = " << my_structure.y << std::endl

<< " z = " << my_structure.z << std::endl

<< std::endl;

struct MyStructure *struct_ptr = &my_structure;

std::cout << "Members accessed through pointer: "

<< std::endl

<< " x = " << struct_ptr->x << std::endl

<< " y = " << struct_ptr->y << std::endl

<< " z = " << struct_ptr->z << std::endl

<< std::endl;

std::cout << "Updating values using pointer" << std::endl;

struct_ptr->x = 100;

struct_ptr->y = 200;

struct_ptr->z = 300;
std::cout << "Members accessed through pointer: "

<< std::endl

<< " x = " << struct_ptr->x << std::endl

<< " y = " << struct_ptr->y << std::endl

<< " z = " << struct_ptr->z << std::endl

<< std::endl;

std::cout << "Updated structure: "

<< std::endl

<< " x = " << my_structure.x << std::endl

<< " y = " << my_structure.y << std::endl

<< " z = " << my_structure.z << std::endl

<< std::endl;

return 0;

Its output:
Figure 33. Compiling and executing object elements' dereferencing

Pointers to functions
Anything having an address in the memory can be pointed to by the
pointers, even functions. Till now we have seen pointers pointing to data
elements and that seems quite simple by now; however, when we point to
functions, pointer notation gets quite complicated, and takes the following
general form:

<function return type> (*pointer_name)(<input parameter types>)

Let us say we need to point to functions that all take two integers as
input parameters and return integer, their pointer takes following form:

int (*pointer_name)(int, int);

The function pointed to by this pointer can simply be invoked like:

int returned_value = pointer_name(5, 10);


To illustrate their usage with a simple example whereby we have
multiple functions that can be pointed to by the same pointer type and pass
them as pointers to another function which in turn calls the function passed
in:

#include <iostream>

int add(int a, int b) {

return a + b;

int subtract(int a, int b) {

return a - b;

int multiply(int a, int b) {

return a * b;

int divide(int a, int b) {


return a / b;

int op(int a, int b, int(*func)(int, int)) {

return func(a, b);

int main() {

std::cout << "20 + 10 = " << op(20, 10, add) << std::endl;

std::cout << "20 - 10 = " << op(20, 10, subtract) << std::endl;

std::cout << "20 x 10 = " << op(20, 10, multiply) << std::endl;

std::cout << "20 / 10 = " << op(20, 10, divide) << std::endl;

return 0;

The function op takes two integers and a function pointer as input


parameters and internally invokes the function through pointer and passes
in arguments, in the end returns the same value as returned by the internal
function call. Its output displays the expected results:
Figure 34. Compiling and executing the function pointers example

This allows creating extraordinarily complex logic quite easily through


managing pointers.
Pointer to character string
A quite common and unique use of pointers you might find often is in
storing text messages as sequence of characters, like:

const char *message = "a message";

std::cout << message << std::endl;

As the pointer only stores its starting address, therefore there must be
something that indicates the end of string – and in this case it is null
termination i.e., zero value at the end of the text string. In the background,
this creates an array of char datatype, and each consecutive byte holds the
ASCII value of consecutive bytes:

0 1 2 3 4 5 6 7 8 9
‘a’ ‘ ‘ ‘m’ ‘e’ ‘s’ ‘s’ ‘a’ ‘g’ ‘e’ 0

Therefore, it creates an array of 10 bytes whereas it has only 9 letters –


the last one has zero value that indicates that the string has ended here. In C
and C++, strings are mostly dealt as being null terminated.
Pointer arithmetic
Arithmetic operations on pointers take special detour from applying
same arithmetic operations on normal variables – especially, it is this part
where size of pointed datatype comes into great play as adding a value to a
pointer semantically means to point to the next value in the memory
expecting values to be placed consecutively one after the other.
Here in this section, we will take examples of addition operations only
as they are most common; whereby, other operations can be deduced from
general working of arithmetic operations on pointers. Let us evaluate the
following example to clarify:

#include <iostream>

int main() {

int values[] = {1, 2, 3, 4, 5};

int *int_ptr = &values[0];

std::cout << "Output format = Address:Value "

<< std::endl << std::endl;

std::cout << int_ptr << ":" << *int_ptr << std::endl;


int_ptr++;

std::cout << int_ptr << ":" << *int_ptr << std::endl;

int_ptr += 1;

std::cout << int_ptr << ":" << *int_ptr << std::endl;

std::cout << int_ptr + 1 << ":" << *(int_ptr + 1) << std::endl;

return 0;

Its output is:

Figure 35. Building and executing the pointer arithmetic example

We have used int_ptr++ , int_ptr += 1 and int_ptr + 1 successively to add one


to current pointer value, whereas each adds four to the value as the pointer
is of integer datatype and integer is having 4 bytes in size – therefore, each
single increment is trying to point to the next value of same type.
Typecasting
Like typecasting amongst different datatypes, types of pointers can also
be cast to other pointer types or even other simpler types; as pointer content
is simple number though representing an address in memory. This makes
pointers even more powerful as any memory location can be treated as any
datatype in fact – for example, treating memory allocated for float like an
integer.
Pointers are surely powerful, therefore, using them calls for assuming
great responsibility as it is quite easy to corrupt program memory when
pointers are not used appropriately.
C/C++ influenced languages like Java, C#; either completely remove the
use of raw pointers or at least restrict their usage with special
instructions or specially marked blocks of code, whereby the developer
assures the compiler of being completely aware of what is being done
with the raw pointers.
Let us take an example of type casting:

#include <iostream>

int main() {

int int_value = 0x00FFEEDD;

double double_value = 3.14159;

unsigned char *ptr;


// Integer's bytes:

ptr = (unsigned char *)&int_value;

std::cout << int_value << " is placed in memory as: ";

for (int i = 0; i < sizeof(int_value); i++) {

std::cout << std::hex << (int)*ptr++ << " ";

std::cout << std::endl;

// Double's bytes:

ptr = (unsigned char *)&double_value;

std::cout << double_value << " is placed in memory as: ";

for (int i = 0; i < sizeof(double_value); i++) {

std::cout << std::hex << (int)*ptr++ << " ";

}
std::cout << std::endl;

return 0;

Here we are encountering few new things – keyword sizeof takes


variable, constant, object, or any datatype and returns its size in number of
bytes. We are using sizeof to programmatically get the number of bytes
being occupied by each variable so that we can loop through and access
each byte of integer and double variables through pointer arithmetic.
In this example we are using single-byte pointer ( unsigned char ) and
casting the addresses of variables (taken by & operator) to the target
pointer type – this makes the pointer to have same value as the value is the
address but allows us to access individual bytes through pointer arithmetic
operations. Int the end, to display the value of each byte in hexadecimal, we
are using std::hex with std::cout .
The output of application makes it more clear on what we are talking
about:

Figure 36. Compiling and running pointer typecasting example

One thing to note here is that the value assigned to integer variable is
printed in reverse order, this is simply because the application is built and
executed on little-endian machine that stores the least significant byte at the
lowest address.
Therefore, we can easily access the byte-level data and additionally can
change each byte. Thus, usage of pointers should always be done
responsibly and with specific purposes.
Classes
In C++, classes are the differentiating factor when compared with C
language – although they can be regarded as an improvement over C
structures, allowing combining data alongside operations within a single
packing as members, while allowing defining accessibility to its members.
You might have heard it before that during earlier days, C++ was
initially named as
“C with Classes,” – denoting the defining feature over C language.
In essence, classes are not simply better structures, they represent a
conceptual improvement that allows a framework of looking at an
application as a collection of objects sitting around in memory, maintaining
their states, and acting on inputs whenever required – thus giving the
famous term: Object Oriented Programming.
A class is simply defined with keyword class , like:

class <class-name> {

};

Rules for naming a class are the same as we have seen for variables and
functions i.e., they can only start with an underscore “_” or an alphabet, and
can only contain underscores, alphabets, and digits. You might find many
conventions used in naming classes, some prefix class names with letter
“C,” and others use simple representative names beginning with capital
letters.
Next comes the accessibility of class members i.e., variables, constants,
and functions. For this, three levels of accessibility are defined, namely:
private , public and protected . Whenever no accessibility is stated, it is private
by default. The accessibilities can be mixed and matched according to
requirements:
class MyClass {

public:

// anything defined from here will be public


private:

// anything defined from here will be private

public:

// anything defined from here will be public - again

protected:

// anything defined from here will be protected

};

In short, public members are accessible from outside the class, if you
define an integer in the public section, it can be accessed from the outside.
Whereas private members are only accessible from within the class i.e.,
these members are private, and no outside code can access these members.
Lastly, protected members are not directly accessible from outside the class
however when the class is having its child classes, the children can access
the protected members.
Let us create a class and see this in action:

#include <iostream>
class ComplexNumber {

private:

int real, complex;

public:

void set(int r, int c) {

real = r;

complex = c;

void print() {

std::cout << real << " + "

<< complex << "j" << std::endl;

};

int main() {

ComplexNumber cn1, cn2;


cn1.set(1, 2);

cn2.set(5, 8);

cn1.print();

cn2.print();

return 0;

We have defined our class named ComplexNumber having two private


member variables that cannot be accessed from outside though can be
accessed by class members. This is to protect the internal data and disallow
external code to manipulate it. Therefore, to set values for private members,
we have a set method that takes inputs as real and imaginary parts and sets
corresponding members. Lastly, we have a print method to simply print the
values to screen in specified pattern. Thereafter, in the main function we
have created two objects named cn1 and cn2 , each takes its own space in
memory thus has different internal variables. That is why, when we set
values on each object and later print them, we get individual values.

When objects are created, each object gets its own set of variables i.e.,
space is only allocated to hold a separate set of variables not the
functions – functions are only defined once and operate on individual
objects.
Constructors and destructors
A constructor is a special function that has the same name as the class
itself and does not return anything – not even a void . Their aim is to
construct or initialize class members during object creation; therefore, they
are only called during object creation. A class can have many constructors,
each taking different sets of inputs thus allowing distinctive styles of
initialization. In contrast, destructors work opposite to constructors and
work when an object is being destroyed. They also take the same name as
the class but prefixed with a tilde “ ~ ” symbol.
Just to stay focused on our learning purpose, we will not go too deep
into nuances of construction and destruction. To clarify, let us take
following example:

#include <iostream>

class ComplexNumber {

private:

int real, complex;

public:
ComplexNumber(int r, int c) {

real = r;

complex = c;

void print() {

std::cout << real << " + "

<< complex << "j" << std::endl;

~ComplexNumber() {

std::cout << "Destroying: "

<< real << " + "

<< complex << "j" << std::endl;

};

int main() {

ComplexNumber cn1(1, 2), cn2(5, 8);


cn1.print();

cn2.print();

return 0;

}
Its output:

Figure 37. Compiling and executing Constructor and Destructor example

It seems quite clear that constructor is invoked during object creation by


passing in parameters – that in turn sets the private members of the class.
This can be verified from the outputs of the print method invoked on each
created object.
The last two lines in the output show the same message as we have
written in the destructor function. Therefore, destructor is automatically
invoked at the end of each object’s life.
Destructors are particularly important from memory and resource
management’s
point-of-view. They allow cleaning-up any internally allocated memory
or other resources that are internally managed by the class.

Static functions
Classes support two kinds of functions – member functions and static
functions. When any function is created normally inside a class, it is
considered as its member function that is intended to be invoked through a
class object as we have seen in the previous examples. However, when a
function is marked as static , then it is not intended to be invoked through
class objects:

class MyClass {

private:

static int var1;

int var2;
public:

static void func1() {

// can only access var1 for being static

// var2 cannot be accessed as it relates to class instances

void func2() {

// var1 can be accessed with MyClass::var1

// var2 is always available as instance variable

};
int main() {

MyClass::func1();

MyClass obj;

obj.func2();

return 0;

This snippet makes the distinction clear – a static function scopes to


class itself therefore it can only access static members and can be invoked
through class scope resolution. In contrast, the member functions are
intended to be invoked through class objects as they can access the class
variables that are defined in every created object.
A notable example of static functions can be in implementing singleton
patterns wherein, a class is required to have one and only instance for any
reasons:

class MyClass {
private:

static MyClass *instance = nullptr;

MyClass() {

/* this constructor is private */

public:

static MyClass * get_instance() {

if (instance == nullptr)

instance = new MyClass;


return instance;

};
As the constructor is private, thereby objects of the class cannot be
created from outside code. Thus, we only have get_instance() which is static
member of the class therefore has access to invoking its constructor. On the
first call to it, it creates the class object and stores its pointer internally,
thereafter, any subsequent calls to it return the already created object
pointer and more objects are not created.

Singleton pattern is one of the quite common patterns used in


programming, a viable place of its use is in providing a single point of
interaction with underlying data or other resources.
“this” pointer
In class constructors, destructors, or member functions – that can access
class object’s members; the object’s pointer is available through special
keyword this . It is especially useful when input parameters are exactly
named as class members, like:

class ComplexNumber {

private:

int real, complex;

public:

ComplexNumber(int real, int complex) {

this->real = real;

this->complex = complex;

void print() {
std::cout << this->real << " + "

<< this->complex << "j"

<< std::endl;

};

As we can see in the constructor, the input parameters are named exactly
as member variables; therefore, to distinguish between input parameters and
the member variables it uses this pointer to refer to the current instance or
object of the class it is being invoked on; same holds true for other member
functions of the class.
Inheritance
Classes can inherit from other classes, the one which inherits is called
child class and the one from which it inherits is called parent class – this in
fact is a powerful thing as many complex things can be modelled by
breaking them into manageable pieces. Let us take following example for
making many concepts clear:

#include <iostream>

class Object {

private:

int x, y;

protected:

Object(int x_value, int y_value) {

x = x_value;

y = y_value;

int get_x() { return x; }


int get_y() { return y; }

};

class Box : public Object {

private:

int w, h;

public:

Box(int x, int y, int width, int height):Object(x, y){

w = width;

h = height;

void draw() {

std::cout << "Box: at "

<< get_x() << "," << get_y()

<< " - "


<< w << "x" << h << std::endl;

};

int main() {

Box b1(1, 2, 5, 5), b2(5, 9, 8, 8);

b1.draw();

b2.draw();

return 0;

Its output goes as:

Figure 38. Compiling and executing the inheritance example

Here we have two classes: Object as parent and Box as its child. The
relationship between classes is given by:

class Box : public Object


It simply tells that Box is being publicly inherited from Object. There
are other types of inheritance but public one is most common.
Few things to note in this example:
1. Constructor of Object is made protected, this allows its children
to access it but disallows directly creating its objects as that
would be meaningless for our purpose.

2. To access private members of parent, accessor protected


functions for x and y are given as protected members, so that
only its children can use them.

3. Initializer list is used for invoking the base class constructor:

Box(int x, int y, int width, int height) : Object(x, y){

An initialization list is provided between closing constructor parenthesis


and starting curly brace of function body, starting with a colon and
separating assignments with commas. For example, constructor of base
class can be written like:

Object(int x_value, int y_value) : x(x_value), y(y_value) { }

Thus, assigning the values from “ x_value ” to “ x ” and “ y_value ” to “ y ”


in initializer list.
Although multiple inheritance is allowed in C++, but it can cause many
side-effects when not managed properly. Thereby, it is recommended to
use single inheritances as possible.

Runtime Polymorphism
Let us consider two features in combination with each other:
1. Any pointer of a base class type can also be used to point to its
children.
2. Any function from base class can be overridden in its children.

Therefore, when a pointer of base class type is holding address of its


child class, in addition the child has also overridden a base class function,
then upon invoking the same function through the pointer, it should
dynamically invoke the child class’s version of overridden function at
runtime – as that would be an intuitive approach. This is achieved by
marking the base class function intended to be overridden with virtual
keyword. Let us see this in action:

#include <iostream>

class Object {

public:
virtual void draw() {

std::cout << "Object draw" << std::endl;

};

class Box : public Object {

public:

void draw() override {

std::cout << "Box draw" << std::endl;

};
int main() {

Object *ptr1, *ptr2;

ptr1 = new Object();

ptr2 = new Box();

ptr1->draw();

ptr2->draw();

return 0;

Its output clarifies intuitions:

Figure 39. Compiling and executing the example of polymorphism


In this example the parent class “Object” has a public function named
“draw” that is marked with virtual ; and the child class “Box” overrides the
method with same name and signature. Therefore, during the execution, we
can see that the right version of the function is invoked.
We can also define pure virtual functions; whereby, a pure virtual
function does not provide any implementation in parent class, therefore, the
child class must provide its implementation.

Any class having any pure virtual function cannot be instantiated,


though its pointer can hold address to its child thereby providing
dynamic function calls.

Newer standards have allowed using override with function signature


while overriding in child class and we have used it in our example,
though with older compilers this keyword can be skipped to retain same
results. This is used for providing compile-time checking whether the
function is overriding a base class virtual function or not.
Namespaces
By default, any names given to functions, structures, classes, global
constants, or
global variables that are defined outside the scope of any class or function
body – in effect all the names given to elements in global scope exist
globally in the codebase and can collide with each other. For example, no
two developers working on the same codebase can name their functions as
“ send_to_output ,” whereas one of them intends to send some values over the
Serial Port and the other wants to send to an attached display device. This
can cause compilation errors unless both developers manage their function
names using some prefixes or suffixes that denote usage or restrict their
scopes to within their compilation units i.e., using static keyword.
Although this is a trivial example, naming things can become quite
complicated in larger projects especially when different developers are
working on the same solution but on distinct aspects of the application.
Additionally, if you get a library written by another developer then you
cannot use the same names for functions or classes that the developer has
already used in the library. This becomes even more complicated when you
need to use multiple libraries from different developers, but they use same
names for some of their functions or classes e.g., using “Base” or “Object”
as base class names.
Therefore, C++ provides a cleaner way of avoiding name collisions
through namespaces and they are quite simple to employ:

namespace <namespace-name> {
}

Rules for naming the namespaces are the same as we have used for
classes, functions, and variables – starting with letter or an underscore and
containing only letters, underscores, and numbers. Though they should
indicate specific purposes. Therefore, any name that comes inside the
namespace is accessed through scope resolution operator i.e., ::

Till now we have used std::cout to print messages on the console, here std
is the namespace in which cout is defined, same goes for std::endl .
Although we can use any name for naming the namespaces, C++
standard libraries use std as their namespace.
A namespace can also have other nested namespaces:

namespace ns1 {

namespace ns2 {

Therefore, any function defined inside ns2 will be required to be


prefixed with its whole namespace ns1::ns2::function_name when it is
referenced from outside the namespace.
Another feature worth mentioning is that, when we do not want to prefix
every function or class name that is inside a namespace, we can use using
namespace <namespace-name>; like:
using namespace std;

This allows using anything inside std namespace by their original


names. However, this works only as long as names do not collide with
others. As an example, let us simplify our message printing:

#include <iostream>

using namespace std;

int main() {

cout << "Now we do not need to use std::" << endl;

}
This clearly simplifies our use of anything inside the namespace. Now
for using the namespaces:

#include <iostream>

using namespace std;

namespace ns1 {

namespace ns2 {

void print_hello_1() {

cout << "Hello World 1" << endl;

void print_hello_2() {
cout << "Hello World 2" << endl;

int main() {

ns1::ns2::print_hello_1();

ns1::print_hello_2();

As we have defined print_hello_1 inside the namespace ns2 and that itself
is inside the namespace ns1 , therefore, to access it we have used scope
resolution with ns1::ns2 . But in the case of print_hello_2 , we have defined it
inside the namespace ns1 itself, therefore it is called by using
ns1::print_hello_2() only.
Type aliasing
For improving code readability and thereby enhancing its quality and
maintainability as a result, C++ provides ways to give datatypes new names
i.e., aliases. Wherein, using aliases can sometimes tremendously improve
code readability. C and C++ support using the typedef keyword and it is still
very commonly used despite new C++ standards allowing using keyword
for the same purpose.
typedef – keyword
Keyword typedef has quite simple format:

Typedef <existing-datatype> <new-name>;

For example, to give a new name to unsigned int :

typedef unsigned int UInt;

Thereafter, we can use UInt as a datatype as usual:

UInt my_var = 10;

This comes handy especially while using function pointers, therefore,


instead of keeping typing int (*pointer_name)(int, int) at all the places where this
type of function is required, we can easily name it as a simpler type:

typedef int (*new_type_name)(int, int);


Thereafter, we can simply use the new type-name. To see this in action,
let us see an example:
#include <iostream>

typedef int Integer;

typedef Integer(*FunctionPtr)(Integer, Integer);

Integer add(Integer a, Integer b) {

return a + b;

Integer subtract(Integer a, Integer b) {

return a - b;

Integer multiply(Integer a, Integer b) {


return a * b;

Integer divide(Integer a, Integer b) {

return a / b;

Integer op(Integer a, Integer b, FunctionPtr func) {

return func(a, b);

int main() {

std::cout << "20 + 10 = " << op(20, 10, add) << std::endl;
std::cout << "20 - 10 = " << op(20, 10, subtract) << std::endl;

std::cout << "20 x 10 = " << op(20, 10, multiply) << std::endl;

std::cout << "20 / 10 = " << op(20, 10, divide) << std::endl;

return 0;

First, we have specified Integer as an alternate name for int , thereafter


whenever we use Integer in the code, the compiler treats it as int . The
second alias we have defined is for the function pointer as FunctionPtr , this
especially allows for cleaner code thereafter.
Another useful place where typedef simplifies code, especially relates
with older C-style using of structures, like:

struct circle {

float x_position;

float y_position;

float radius;

};

Wherein, older C-style always uses struct keyword with the name of
structure even while defining the objects. This can be simplified by:

typedef struct circle {

float x_position;

float y_position;

float radius;

} new_name;
Thereafter, the new_name can be used as a type that refers to the
structure.
using – keyword
In addition to using the “ using ” keyword with namespaces, C++ has
extended its use for type aliasing as well, though the purpose is the same. It
takes following general form:

using <new-name> = <existing-type>;

Without going into the details, let us rewrite our previous example with
newer keyword:

#include <iostream>

using Number = int;

using FunctionPtr = Number(*)(Number, Number);

Number add(Number a, Number b) {

return a + b;

Number subtract(Number a, Number b) {

return a - b;
}

Number multiply(Number a, Number b) {

return a * b;

Number divide(Number a, Number b) {

return a / b;

Number op(Number a, Number b, FunctionPtr func) {

return func(a, b);

int main() {

std::cout << "20 + 10 = " << op(20, 10, add) << std::endl;

std::cout << "20 - 10 = " << op(20, 10, subtract) << std::endl;

std::cout << "20 x 10 = " << op(20, 10, multiply) << std::endl;

std::cout << "20 / 10 = " << op(20, 10, divide) << std::endl;
return 0;

This simple and quite familiar example by now, utilizes“ using ” to create
type aliases for int as Number , and for the function pointer as FunctionPtr – it
also seems a bit more intuitive than previously discussed “ typedef ”
keyword.
A note on compiler directives and switches
Till this point, we have used the compiler as a Blackbox for just creating
executables from source files. Though compilers themselves are software
applications and provide various facilities that help in writing and
organizing code better, some compilers can even provide additional features
in addition to standards for certain use-cases. Therefore, to understand a
compiler’s nuances its documentation must be consulted. However, in this
section, we will look at the things that compilers do with the code during
distinct phases of compilation.
#include

We have used #include in all our sample applications, especially #include


<iostream> for using std::cout etc., but what it actually does is just copy and
paste operation – that’s it. It takes two forms:

#include <file-name>

And

#include “file-name”

The difference however is that the first one looks at installed compiler
libraries to search for specified file then in other locations if it does not find
it there; however, the second one looks for the specified file in current path
and then if it is not found the compiler looks for it in standard libraries path.
In both the cases, when the file with specified name is found, its content is
copied and pasted at the location where #include is used.
To demonstrate this, let us say we have comma separated values in a file
that we need to use as an array’s elements. Let us place following values as
example in values.csv file, extension does not matter here as it is just the part
of file’s name:
1, 2, 3, 4, 5, 6, 7, 8, 9
Thereafter, in our “ .cpp ” file:

#include <iostream>

int main() {

int my_array[] = {

#include "values.csv"

};

for (int i = 0; i < sizeof(my_array)/sizeof(int); i++) {

std::cout << my_array[i] << " - ";

}
std::cout << std::endl;

return 0;

Here we have not provided the values directly but have used #include to
get the values as content from another file; though for computing the
number of values, we are using sizeof operator – thereby, dividing the total
number of bytes taken by the array with number of bytes taken by each
element gives total number of elements; that’s what we are using to iterate
over each element. Hence, the output shows:

Figure 40. Compiling and executing the file inclusion example

For providing additional search paths to compiler so that it can look for
“included files” in places other than current path or standard libraires’
path, Command-line switches are used. To get exact details on
command-line switches, try using g++ --help or see your compiler’s
documentation for complete details.
Macros
Macros provide a way for in-code replacements i.e., replacing one
symbol with another piece of code before compilation takes place for code
generation. Their generic format is:

#define ORIGINAL REPLACEMENT

Wherever ORIGINAL comes in code it gets replaced with REPLACEMENT ,


which itself can comprise of multiple lines each ending with a backslash
“ \ ” denoting that the next line is still the part of current line:

#define ORIGINAL REPLACEMENT-LINE-1 \

REPLACEMENT-LINE-2 \

REPLACEMENT-LINE-3 \

REPLACEMENT-LINE-4

These can take parameter as input:

#define ORIGINAL(VALUE) REPLACEMENT-USING-VALUE

Note: there is no space between ORIGINAL and the starting parenthesis


of (VALUE) ; as any space after ORIGINAL would start the replacement.
Here, an example can make more sense in clarifying things:

#include <iostream>

using namespace std;

int function_add(int v1, int v2) { return v1 + v2; }

int function_sub(int v1, int v2) { return v1 - v2; }

int function_mul(int v1, int v2) { return v1 * v2; }

int function_div(int v1, int v2) { return v1 / v2; }

#define V1 20

#define V2 10
#define op(oper,value1,value2) function_##oper(value1,value2)

int main() {

cout << V1 << " + " << V2 << " = " << op(add, V1, V2) << endl;

cout << V1 << " - " << V2 << " = " << op(sub, V1, V2) << endl;

cout << V1 << " x " << V2 << " = " << op(mul, V1, V2) << endl;

cout << V1 << " / " << V2 << " = " << op(div, V1, V2) << endl;

return 0;

Output generated by the example code:

Figure 41. Compiling and executing macros example

First, we are using V1 and V2 instead of 20 and 10 respectively. The


tricky part is op macro – it takes three elements as input, appends the first
passed in text using ## to function_ and calls the resultant as function by
passing it the rest two elements as arguments. Therefore,
op(add, V1, V2) expands as function_add(V1, V2) and that is a valid function call.
Conditional compilation
Lastly, we have compiler directives in the form of #if..else..endif and #ifdef
– Whereby, #if evaluates its following condition, if it evaluates to Boolean
true then the following part till #endif is made part of compilation otherwise
it is skipped:

#if SyMBOL == VALUE

// code

#else

// alternate code

#endif

#ifdef is shorter form for #if defined(SYMBOL) and #ifndef is shorter form for
#if !defined(SYMBOL) – working opposite to each other. Their most significant
use is in conditional compilation thereby allowing certain pieces of code to
be excluded from compilation when compiling for certain targets or
replacing certain pieces of code for certain targets – for example, GNU
Linux and Windows have different API calls for using sockets, therefore for
making the code cross-platform compatible we can replace API calls in
relevant pieces of code – thereby, making the codebase relevant for multiple
platforms.
Another quite common use is preventing multiple compilations of same
piece of code – especially, we know that header files are simply copied and
pasted during compilation. Therefore, when one header file internally
includes another header file and we use both in our compilation unit, we
can have multiple definition issues. So, to skip already included part, we
can guard using compiler directives:

#ifndef UNIQUE_SYMBOL_MAYBE_FILE_NAME_WITH_PATH

#define UNIQUE_SYMBOL_MAYBE_FILE_NAME_WITH_PATH

// content here is protected now

#endif

This way of guarding against multiple inclusions can quite commonly


be seen in variety of codebases.
Splitting code into multiple files
For better management of code, it needs to be split into multiple files, a
lot of new developers struggle with this idea and try to put everything inside
an only source file. Therefore, the way will look at it is using an example:
First, for using library functions we can declare them in my_library.h
header file:

#ifndef MY_LIBRARY_H

#define MY_LIBRARY_H

int get_number_from_user();

void print_table(int);

#endif

Using compilation guard, we have declared two functions here. Now


their implementation goes into my_library.cpp :

#include <iostream>
using namespace std;

int get_number_from_user() {

int value;

cin >> value;

return value;

void print_table(int t) {

for (int i = 1; i <= 10; i++) {

cout << t << " x " << i << " = " << t * i << endl;

These simple implementations are already familiar to us, here we have


only separated them into separate code file. Therefore, in our main.cpp file,
we can use declarations in header file to remain compile-able:
#include <iostream>

#include "my_library.h"

using namespace std;

int main() {

int table;

do {

cout << "Which table do you want? (0 or -ve to exit) ";

table = get_number_from_user();

if (table <= 0) break;

print_table(table);
} while (1);

Lastly, we create Makefile to build this simple project:

all: main my_library

g++ main.o my_library.o -o tables

main:

g++ -c main.cpp -o main.o

my_library:

g++ -c my_library.cpp -o my_library.o

clean:

rm main.o

rm my_library.o

rm tables

Note that for building the whole project, we have the default target “ all ”
that depends on “ main ” and “ my_library ” targets in which we are using -c
switch with g++ to tell it to only produce object files. After building of
these dependencies, “ all ” executes that finally connects the generated
object files together into creating the executable.

This same concept can be extended to any number of files we have.


Many IDEs generate the Makefile automatically, depending on provided
project settings – though, generally there is no need to mess with these
auto-generated files.
Chapter 5:
Understanding Basic Hardware

O ther than the coding part that we are covering in detail in this book, it
ultimately relates to interacting with the physical outside world outside
the microcontroller. When it comes to interacting with the physical world
through devices of the Internet of Things, understanding basic electronics
becomes more important in the sense that frequent updates at this level are
costly as they normally involve hardware parts replacement, instead of just
writing and shipping software updates. If you are coming from an
electronics background, please skip this chapter, as it would not offer much
for someone with strong background knowledge in this field.
Electronics as well as Feedback and Controls are vast subjects and
numerous books, and other material are available if you are seeking to build
an in-depth understanding; a single chapter of a concise book can never
suffice in providing all the aspects of this knowledge domain. However,
salient recurring and common features are being summarized to start with
using IoT as is the theme here.
Before we proceed further, it would be nicer to build understanding by
abstracting the devices as just providing an interface and functionality.
Abstraction is the key here – by abstracting away implementation details of
underlying hardware while understanding basic input and output
characteristics plays considerable role whenever employment of knowledge
is concerned. Here we will look at basic electronic and control devices that
are commonly required in implementing recurring scenarios – in an abstract
manner.
Power Supplies
Deployed devices always require power sources for working; whether it
be from solar energy, wind turbines, fuel powered generators, batteries, or
main supply lines, the power requires conversion from higher potential (say
110/220-volt AC) to required voltages (e.g., 5-to-9-volt DC). After
conversion to required DC voltage, voltage regulation to keep fluctuations
away from the sensitive equipment is also a requirement. Let us say, if the
equipment is powered from batteries, the battery voltages change depending
upon their held charges and decrease during discharge; therefore, to keep
voltages in certain range, regulation is required.
Just as a reminder from school days: AC is Alternating Current which
keeps changing polarity in cycles i.e., going from negative to positive
potential and reverse; and DC stands for the
Direct Current which keeps polarities of supply wires same i.e., positive
wire will always stay positive and negative will always stay negative.
Transformation of available AC power source requires stepping-it-down
through a transformer or other circuits, to bring high potential to low
voltage levels that makes it manageable for following circuit. Stepped-
down voltages through a transformer are then passed through bridge
rectifier circuit to convert it into DC, followed by conversion to DC, filters
are added to the circuit to reduce ripples in the voltage before supplying
power to final circuit. The power is also regulated through voltage regulator
to keep supplying voltage to driven electronics within specified range.
This is simply said than done, therefore, without trying to reinvent the
wheels, we can simply use readily available power supplies in the market
because they are commonly and readily available in the form of packages;
and can provide amicable results. Just purchase one according to
requirements, otherwise, if very peculiar requirements need to be served
and exact rating is not available, get one with closest required rating and
use a Buck (voltage reducing devices) or
a Boost Converter (voltage increasing devices) – also commonly available.
Unless there are some specific requirements, generally required power
supplies are commonly available. Commonly available power supplies,
generally serve most if not all purposes at cheaper costs when
production volumes of specially built supplies are low.
The main part is coming up with power requirements first, by looking at
maximum power requirements of target device circuitry – including
voltages and current; then finding the closest in upward side of available
power supplies that can provide at least 30 percent extra power (voltages
multiplied with current) as safety factor and more power can be
considered for expansion of circuitry at later stages.
Resistors
Resistors are one of fundamental building blocks in electrical circuits
and draw their name from their intended purpose of providing resistance to
flow of electrons. The measurement of the resistance they provide in the
flow of electric current is mentioned as their rated values represented as
measurement unit of Ohm (Ω) and depicted commonly with color bands on
each resistor. Their common usage in electronics is current limiting, voltage
division, biasing of active elements like transistors, and power dissipation
by acting as pure loads in power circuits – for being just passive devices;
however, resistors are a bit complex in employment. Commonly schematic
diagrams depict resistors with two-legged boxes or with a zig-zag pattern
having terminals at both the ends.

There are many configurations in which resistors can be used; for


example, in a series configuration having one terminal from each resistor
joined with another’s single terminal, consequently using the ending free
terminals in the circuit. In a series combination, total resistance adds up,
whereas the total resistance reduces in parallel configuration, wherein all
the resistors are joined together in such a way that their terminals join on
both sides.

Figure 43. Series (left) and Parallel (right) combination of resistors


A quite common configuration of resistors is working as a voltage
divider:

Figure 44. Resistors as voltage divider

Here voltages across two resistors divide as direct proportion of


individual resistance to the total combined resistance in series
configuration.

For example, if VR1 and VR2 are required to be the same i.e., dividing
the voltages equally, then both the resistors are selected with equal rated
values. Two resistors having 1k Ω resistance can divide the total voltage
of 9V into two equal values.

Larger the resistance in this combination is, voltages across it will be


equivalently larger.
Diodes and LEDs
Diodes are a type of semi-conductor device having a P-type and an N-
type semi-conductor material with a junction between them. Without
getting confused, we can abstract away this information by stating that the
diodes are a type of devices that only allow current to flow in one direction
and stop the flow in reverse direction. When current flows through a diode,
the diode is referred to as being forward-biased, and in the other state in
which the current is stopped from flowing through it, the diode is referred
to as being in reverse-biased state. Since current can only flow in one
direction; therefore, their connection in circuit needs attention. For the same
reason, their terminals are denoted by “A” (Anode) and “K” or “C”
(Cathode).

Figure 45. Symbol of a diode

Diodes come in different shapes and sizes and are often marked with a
band near one of its terminals; their schematic symbol is denoted as an
arrow that depicts the allowed direction of current flow.

Figure 46. A triangular wave passing through a diode

Therefore, when a signal is passed through a diode, it allows only the


portion of the signal that makes it work in forward-biased state to flow
through it, and not in the opposite state. This behavior allows us to use
diodes as rectifiers that convert AC to DC by separating positive and
negative cycles of AC. Additionally, their use in other protection circuits is
also quite common, whereby they do not work in normal state, and only
start working when needed to dissipate current – this behavior is mostly
required when working with electromagnets.

Every time when an electromagnetic device is required to be switched


on and off through electronic circuits, they require using diodes to
protect the electronic circuit from excessive voltages produced due to
electromagnetic properties.
Light Emitting Diode (LED) are commonly used in all kinds of
electronic circuits, and they simply are diodes. Their difference from other
common-use diodes is that LEDs emit light when they are working in
forward bias state. Other than just emitting light as individual components,
LEDs commonly find their use in displaying status and displaying numbers
using seven-segment displays.

Figure 47. A seven-segment display

Seven-segment displays can be found commonly in computer


motherboards, where they display Q-Codes or error codes that are
helpful in diagnosing issues when systems fail to start.
Inductors and relays
Inductors or coils are electromagnets having units of measurement in
Henry and denoted by letter “L” in literature and schematics. For explaining
in simple words, inductors are insulated wires that are wound typically
around ferromagnetic materials; however, their complete explanation
requires understanding of Electromagnetic Theory. Inductors can be
abstracted as just the electromagnets that become magnets when current
flows through them and get deenergized or become non-magnets when
current stops flowing through them.
An important concept to understand before using inductors is to
understand that they resist any change of current flowing through them, and
this behavior relates with their electromagnetic characteristics. This makes
it compulsory for us to use some discharging mechanism whenever
inductors are used in circuits, because when they are switched off from
magnetized state, a reverse electromagnetic force is produced that can cause
severe damage to electric circuits.
What interests here is that: inductors, when become magnets during
current passage, they can initiate other circuits. An example of such devices
is relay switches – a relay switch is simply a switch that can be turned on or
off by electric signals, thereby, they can start or stop any electrically
powered device like lights, televisions, and motors.

Figure 48. A simple Relay Switch

Shown in figure is a typical relay; when the coil is not energized and
current is not flowing through its wire, its terminals C and 2 are connected;
and when the coil is energized by passing current, the current makes the coil
a magnet, therefore, the magnet pulls movable part of the terminal C
towards itself, this connects terminal C with terminal 1. This arrangement is
common in controlling power electric devices.

Relay switches are available in various ratings – for controlling heavy


machineries high powered relays are used, and for small electric motors,
little relays are commonly available.
Selection of a relay is purely done based on target peak load, and their
connections with microcontrollers require particular attention, as they
require additional drive circuits.

For controlling powered devices, another class of Solid-state Relays is


also commonly available, and many of their variants can be directly
controlled through microcontroller pins.
Capacitors
Capacitors comprise of two closely held conductive plates separated by
a dielectric material. They are denoted by letter “C” having their
capacitances measured in Farads with common availability in ranges of
micro-Farads denoted by µF. This arrangement of parallel but unconnected
plates makes capacitors perform differently in different scenarios; for
example, capacitors can pass alternating current but stop direct current,
capacitors can allow signals to pass through them whereas they stop signal’s
DC components, capacitors can act as filters in combination with resistors
using RC combination, and capacitors can reduce ripples in signals.

Capacitors are used alongside inductive devices like AC motors to


correct their power factors.

Capacitors come in various shapes, sizes, and ratings; and are


commonly denoted in schematic diagrams as two parallel plates close to
each other.

Figure 49. Schematic symbol of capacitor

Their most common use you might find in IoT controls and related
circuits might include them as filters having two or more capacitors
connected in parallel with microcontrollers or other devices to filter out any
noise components at different frequency notches. Other places where
capacitors are found include clock generation circuits and timing devices
which use their charging and discharging properties to generate timing
signals.

Use of capacitors in basic automation is subjective, thereby we can


always find in OEM’s datasheets about when and where to use
capacitors with specified ratings.
Transistors, FETs and MOSFETs
Bi-polar Junction Transistors (BJTs or just transistors), Field Effect
Transistors (FETs) and Metal-oxide Semi-conductor Field Effect Transistors
(MOSFETs) are the class of devices that are building blocks of modern
electronics. These, especially transistors marked the beginning of modern-
day computers due to their superior performance and ridiculously smaller
sizes as compared with their predecessor vacuum tubes. Although we are
not designing a microprocessor right now, but still these devices are needed
when it comes to controlling the outside devices using digital controllers.
We can look at these devices in an abstract way; wherein, all these devices
are used to control the flow of current depending on some control input.
Transistors are available in two types i.e., NPN and PNP; however, for
our control purpose, we will only look at using an NPN transistor for
controlling a relay switch, for the purpose of going straight to the point of
controlling physical devices without going too deep into understanding all
these devices.
For switching on a relay, or a motor, a good amount of power is required
when compared with output signals from microcontrollers which only
provide an order of few milli-amperes of current at 3.3 to 5 volts. Whereas
electromagnets require many times more current than provided by
microcontrollers and much more voltages to work. Therefore, some
mechanism is needed for translating switching on and off signals from
microcontrollers into operating these devices without overloading and
damaging the controllers. It is where transistors and other devices come into
play:

Figure 50. Transistor controlling a relay switch based on microcontroller command

Transistors are normally denoted by letter “Q” and their three terminals
are:
C (Collector), B (Base) and E (Emitter); to understand in simple words for
this NPN transistor: whenever base voltages are higher than emitter voltage,
current starts flowing from base to emitter, this phenomena switches on the
transistor when collector voltages are much higher than the base voltages,
therefore, current starts flowing from collector to emitter.
In this circuit, a relay switch is required to be switched on and off with
signals from microcontroller (MC) and signal voltages typically range from
0 to 5 volts with 20 milli-amperes of maximum current. After passing
through a resistor of 220 Ω, the signal is fed into the base of transistor.
When signal is off i.e., 0 volt, it makes transistor stop current flow from
collector to emitter thereby it switches off the relay; and when
microcontroller signals to switch on the relay by providing 3.3 to 5 volts
signal, it makes the transistor switch on, resultantly current starts flowing
through the relay coil and the transistor, this in turn allows the light to
switch on by providing it with 110 volts AC.
The diode we are using in this circuit provides protection to the
transistor. Whenever the relay is switched off from on state, this diode tries
to dissipate backwards generated electromagnetic force i.e., high voltage,
produced due to electromagnetic effects to resist changes in current flow
through the coil. In this configuration the diode is commonly called a
Flywheel diode.

Usage of Metal Oxide Varistors can be found in circuits for protecting


from back EMF – any option i.e., MOV or Flywheel diodes can be
utilized depending on requirements.

When the devices that are required to be controlled with heavy currents,
and you cannot find transistors to support that much current, do not
bother by keeping dragging your solution in search of more and more
powerful transistors, you probably would not find very powerful
transistors. Instead, look for other solutions like using transistors to
operate FETs or MOSFETs that can handle heavier currents, and then
using them to operate required devices.
You can also use a transistor to operate a small relay and using that relay
you can control a bigger circuit.

Using transistors in a special combination is called Darlington Pair in


which two transistors are combined in a manner to increase their overall
power handling capacity.
Analog to Digital Converters
An analog to digital converter (A2D converter) simply helps in
converting analog signals into digital quantified forms i.e., a combination of
1s and 0s. For example, if we have an analog signal that varies from 0 to 5
volt; now, when we quantify its value within this range, we first need to
divide this whole range into discrete steps: let us say that we divide it and
have steps: 0 to 1 volt, 1 to 2 volt, 2 to 3, and so on; after division, each
division is assigned incremental binary numbers like, 000, 001, 010 etc.;
lastly, when the signal is quantified the corresponding division’s binary
representation within which the signal’s voltage value is lying at the
moment, binary representation becomes the digital representation of the
signal in that moment.
Reading an analog signal’s chunk is called sampling, the number of
samples taken in one second refers to sampling rate, and the number of bits
that are used to represent each division define the resolution of A2D
converter – it is a measure of how closely we are getting an analog value
represented in digital format.
For example, if we use a 10-bit A2D converter and voltage range is
from 0 to 5 volts, then the entire range is divided into 1024 equally spaced
intervals; each separated by 0.00488 volt. Therefore, if input voltage is 0,
its corresponding digital value is also 0; and when input voltage is 2, its
corresponding digital value becomes nearly 2 ÷ 0.00488 ≈ 410, because
each division amounts to roughly 0.00488 volt. Conversely, if we know
digital value, we can easily find corresponding voltage value as well.
However, if we use eight bits to quantify values within the same voltage
range, the range is divided into 256 steps as it is the total number of values
that can be represented with
8-bits. Thus, it reduces the resolution from 1024 steps that we had with 10-
bits representation.
The required resolution selection depends on target requirement as to
what level of precision our system needs to work. The second consideration
in selecting A2D converters is how many samples need to be taken from the
signal in a second.
The Nyquist Sampling Theorem advises that, for getting accurate
information from an analog signal the sampling rate must be at least
twice the frequency of highest frequency component in the waveform
being sampled."
To reconstruct a sampled signal that is changing two times per second, it
must at least be sampled at a rate of not less than four samples a second,
obviously, more samples provide more information.
In audible voice frequencies, normally human ears can listen from 20 Hz
to 20 kHz, and the range varies with age and other factors; therefore,
digital audio systems are common to sample voice data at 44.1, 48 and
96 kHz to capture the closest details while simplifying filter designs and
without gathering too much voice data.
Microcontrollers available these days commonly include A2D
converters within chip, additional ones can be attached externally.

Digital to Analog Converters


The reverse process of A2D converters is D2A or digital to analog
converters.
In Digital Signal Processing, signals are converted into digital form,
processed using various digital signal processing algorithms, and
converted back into analog form.
D2A converters are mostly used to generate any desired analog signal –
output can be any kind, like, audio, video, and control signals for motor
driving circuits. Here precision also depends on the number of bits being
used to represent output voltage, and the range of voltages that needs to be
produced from interpretation of digital bits.
Compared with A2D converters, D2A converters are less common in
microcontrollers. Nevertheless, they can be added as external devices
with microcontrollers and can take digital input over serial
communication or in parallel configurations for producing analog
outputs.
Sensors
Sensors play a vital role when devices interact with their environment.
Wide array of sensors is available to take inputs from environments in
different domains. For example, in light sensing Light Dependent Resistors
for detecting light intensity, IR detector for detecting infrared, photo sensors
in cameras to take pictures for applying image processing, water level,
humidity, temperature, air pressure, accelerometers, gyroscopes, SONAR,
and many more. A wide array, and general availability of sensors makes it
quite easy to take inputs in various domains and train devices according to
environments. It takes the right combination and right precision of sensors
to effectively design solutions.
Generally, sensors vary electrical properties of materials, like an LDR
changes resistance value with varying light intensity; and some sensors
convert sensed energy into voltages or current, like a solar cell converts
solar light into voltages and hence can be used to detect solar light intensity.
Existing devices like variable resistors and variable capacitors if
attached with other physical phenomenon, can allow us to measure physical
changes in form of their changing electrical properties. For example,
attaching a pivoting door with a variable resistor in a manner that pivoting
of door changes its resistance; hence, measuring changes in resistance can
provide information about door’s angular position, and can be read in using
an analog to digital converter, like:

This simple voltage divider can help in reading angular changes for
pivot door. As the pivot angle changes, it changes resistance value, which in
turn changes voltages at analog input of A2D converter. Though, more
sophisticated methods with precise sensors are also available.
Actuators
After taking inputs from sensors, analyzing situation, and taking
decisions; execution of decisions is done by utilizing actuators, for
example, running a DC motor using following circuit:

Figure 51. Sample circuit to control a motor

Actuators can be either simple DC motors, stepper motors, AC motors


for heavy loads, BLDC (Brush-less DC Motors) for efficient and silent
working over longer periods of time, or combinations of motors with
mechanical hydraulic or pneumatic devices. However, the common thing is
that electromagnetism is involved just like relays; as these devices
commonly require much heavier currents, therefore, depending on motors,
you can find their driver circuits that are pre-packaged circuits to make
interfacing the actuating devices easy with microcontrollers.
intentionally left blank
Chapter 6:
Understanding Arduino

S ince its humble beginning as an open-source hardware and software


development project for empowering everyone in developing electronic
automation solutions, Arduino has expanded quite a lot into becoming a
formidable platform and an ecosystem for building hobbyist, educational as
well as professional grade solutions. Arduino’s developed hardware also
comes in these categories targeting various use-cases in professional cloud-
based automation and machine learning solutions, and the basic devices that
can be used in prototyping and developing do-it-yourself kind of projects.
Additionally, Arduino software development solutions are compatible with
various other hardware manufactured by other Original Equipment
Manufacturers.

Figure 52. Arduino's industrial grade Nicla board (Source: Arduino site)

Development cycle of projects with Arduino family of solutions


involves using:
1. Arduino IDE to orchestrate all the activities of writing code,
building the solution to generate binary file, and uploading the
generated binary file to attached device through USB port.

There are two major versions i.e., 1 and 2, available for Arduino IDE as
of now. IDE major version 1 is considered legacy one – however, we
will be using it in our examples. Though you might use the newer
version, but it will have certain differences.

2. Open-source libraries are available for interfacing various


sensors and displays and other communication devices. The
libraries distributed through Arduino’s ecosystem are quite easy
to integrate and require just downloading inside the IDE,
including their header files, and using their provided
functionality.

3. Easy-to-use hardware boards are available from Arduino that


have minimal circuitry to power-up their soldered
microcontroller chips and allow to upload a built binary through
USB port onto the chip. Their various categories in professional,
industrial, or general-purpose domains can be consulted through
official Arduino site, in addition to their board descriptions and
hardware specifications – board descriptions are used during
application development, and hardware specifications are used
in attaching external hardware.

4. Diverse types of hardware are readily available from Arduino


itself and through other
third-party sources – like, sensors, displays, data transceivers,
motor drivers, relays, etc. Depending on target application
requirements, external hardware can be connected with Arduino
boards and controlled through the code burnt into its controller.
Keep in mind the specifications of hardware before attaching any device
with Arduino board, make sure that its drive current and voltages are
within the allowed limits of installed microcontroller; otherwise, over
current or excessive input voltages can permanently damage your
microcontroller.
Environment setup
Setting up the Arduino development environment is just about installing
a standard Windows application; wherein, all the tools for building and
uploading the applications are packaged together and installed by default.
For this, just go to Arduino website and it will guide through all the steps
required for installing Arduino IDE according to the underlying desktop
Operating System. However, we will look at its distinctive features in this
section.

First look at the IDE


Once the Arduino IDE is installed and opened it looks like the
following:
Figure 53. Initial look of Arduino IDE

In this book we are using the legacy Arduino IDE version 1.* , you can
use the newer
version 2.* of the IDE; however, there might be certain differences.
Although, both the versions are available online.
Initially the IDE gives sample code that only has two blank functions
named setup() and loop() . The code files that the Arduino IDE associates its
code with have extension“ .ino ” and are called “Sketches” in Arduino lingo.
These two functions i.e., setup() and loop() are the only required functions in
a sketch. If you need to see that how these functions get linked with
backend code “File” menu and select “Preferences”:

Figure 54. Selecting "Preferences"


In the preferences, check “compilation” in “Show verbose output
during:” options:

Figure 55. Selecting verbose output during compilation

Selecting this option will display compilation commands in lower part


of the IDE. Now, select OK button to save the settings and close the
“Preferences” window. Let us press “Verify” button in the IDE, this starts
building the project and we van see which commands and which library
files it is using in building the project:
Figure 56. Compilation output

Here we can see which files are being compiled and where they are
located. Let us open the indicated “ main.cpp ” file, it contains the main()
function in our case and looks somewhat like the following snippet:

int main(void)

// some initialization code

setup();

for (;;) {
loop();

// some other code

return 0;

In this piece of code for(;;) indicates an infinite loop. Thus, it is this


piece of code that invokes a sketch’s setup() function only once after
initializing the controller and then keeps invoking the loop() function inside
an infinite loop.

Using the same method try reading the Arduino libraries – you would
surely come across many new things; probably this is the best way to
understand what Arduino is doing under the covers to make
development experience easy for everyone.
Libraries
Arduino ecosystem is quite huge – many libraries you can find
distributed through Arduino IDE’s Library Manager, and many are
available through other open-source projects. To install libraries, select
“Manage Libraries…” option under “Tools” menu:

Figure 57. Manage Libraries option


This opens the Library Manager window:

Figure 58. Library Manager

Depending upon the requisite task or hardware you are looking to


integrate with Arduino, there are quite solid chances that you might find
requisite libraries ready to be used in your project. Just install it and see its
documentation that will discuss about the functionality it provides and the
header-files required to be included in your code for using it.
Windows Remote Arduino with StandardFirmata
As our first interaction with Arduino hardware, let us use readily
available application from Microsoft Store named “Windows Remote
Arduino Experience.” Search for it and install it from Microsoft Store:

Figure 59. Installing Windows Remote Arduino Experience from Microsoft Store
Now, before running it let us load the attached Arduino board with
specific firmware – and it is given with Arduino IDE as examples, just open
the Arduino IDE, open the File menu, and select the example as shown:

Figure 60. Selecting the example


Now that the example code is opened in new window, attach the
Arduino device through
USB port and select its type and assigned port in the IDE:

Figure 61. The target board selection in Arduino IDE


Similarly, select the serial port:

Figure 62. Selecting the target port in Arduino IDE


Now you can simply upload the sketch using upload button in Arduino
IDE:
Figure 63. Uploading the sketch to the target device
After successful uploading of sketch, the device is ready to be used with
Windows Remote Arduino Experience, thus open the application, select as
follows and press the “Connect” button:

Figure 64. Selecting the device in Windows Remote Arduino Experience


Once the device connects successfully, its controls are made available
with easy-to-use interface wherein we can simply select different options to
control different outputs and inputs of the connected device:

Figure 65. Device connected through Windows Remote Arduino Experience

In testing the hardware features even before writing code, such tools can
prove quite helpful in developing projects.
Default library functions
Before commencing with the library functions, let us first see an
Arduino board:

Figure 66. Arduino Mega 2550 rev3 Board (Picture from Arduino site)

On the board, we can see that many pins are marked with numbers like
0, 1, 2, …, these are the digital pin numbers that need to be used while
developing applications. Some pins are denoted with numbers prefixed by
“A” like A0, A1, A2, …, these are analog input pins as written above them
as well, any analogue input connected with them is digitized by the internal
Analogue to Digital Converter though the input must be in specified range
and reference voltages should be set in accordance with board
documentation. Additionally, you can find some pins marked as PWM or
their digital pin numbers prefixed with tilde “~” symbol, these are used for
generating Pulse-width Modulated outputs i.e., for controlling motor speeds
or light intensities etc.

If multiple functionalities are allowed on a single pin, it can only be used


for one functionality at a time and that is controlled through application
running on the microcontroller.
Pins on which serial communication can be done are marked with TX
and RD appended with port number like TX0 and RX0; and other
communication protocols if supported by the microcontroller, their
corresponding pins are accordingly marked like in this board SCL, and
SDA denote that I2C (Inter-Integrated Circuit) communication is supported
on this board through the SCL (Serial Clock) and SDA (Serial Data) pins.
While using an I2C bus, remember to use pull-up resistors or consult
microcontroller documentation.

When a serial port is being used for communicating with PC’s serial
monitor, its corresponding digital pins should not be used for any other
purpose; otherwise, communication problems can become a headache
while tracing.
Library functions
Arduino library has been growing since its inception; therefore, it is
advisable to visit official Arduino library documentation on its site for full
range of documentation. However, here we will cover certain library
functions to get started with Arduino. Following list covers very typically
used functions:

Function Purpose Example use


Digital and Analog I/O
Sets the specified pin to
pinMode
INPUT , OUTPUT , or pinMode(13, OUTPUT);
(pin, mode)
INPUT_PULLUP mode
digitalRead Reads the digital pin as
int v = digitalRead(3);
(pin) HIGH or LOW
digitalWrite Sets the output pin to
(pin, value) digitalWrite(13, HIGH);
HIGH or LOW
Returns the analog to
digital converted value
from specified pin –
analogRead
(pin)
correct it according to int v = analogRead(A2);
resolution e.g., divide
by 1024.0 for 10-bit to
get actual value
Sets the PWM pin’s
duty cycle; or when the
pin is of Digital to
Analog Converter it sets
the output analog value.
analogWrite
(pin, value)
PWM frequency analogWrite(13, 150);
depends on the
controller, and its duty
cycle value can be from
0 (OFF) to 255 (always
ON)
analogReference Sets the analog analogReference(EXTERNAL);
(type) // using AREF pin’s
reference voltage for
// voltage
A2D conversions.
Each microcontroller
has different settings,
therefore, consult
documentation.
Time delays
Pauses program
delay
(time) execution for given delay(1000);
time in milliseconds
Pauses program
delayMicroseconds
(time) execution for given delayMicroseconds(1000);
time in microseconds
Execution time
Returns the total
number of milliseconds
elapsed since starting
execution of program.
The value’s datatype is
unsigned long therefore
it overflows after unsigned long tt =
millis()
reaching maximum millis();
value and starts
counting from zero
again. Though the
maximum value
represents
approximately 50 days.
micros() Returns the total unsigned long tt =
number of micros();
microseconds elapsed
since starting execution
of program. The
value’s datatype is
unsigned long therefore
it overflows after
reaching its maximum
value and starts
counting from zero
again.
The maximum value it
can represent is
approximately
70 minutes.
The time resolution
depends on
microcontroller and
thus the board.
By any means this is not a complete list of library functions – Arduino
library itself is quite huge; therefore, for a complete list please consult the
documentation. Here, only a few functions are listed to get us going
further.
First Arduino project
Just like in learning programming languages it is customary to write a
“Hello World” application; in the hardware world blinking an LED
becomes a “Hello World” equivalent. But before we begin, let us take
following points in consideration:
1. Arduino refers to its code files as sketches, and they take “ .ino ”
file extension; these are simple text files and can be edited using
any text editor.

2. Arduino requires that the folder’s name in which sketch file is


located, should also be named as the sketch file is named. For
example, if sketch file is named “ my_prog.ino ” then the sketch
should be placed inside a folder named “ my_prog ” as well.

3. The folder can contain other code files like “ .c ” and “ .cpp ”;
these are automatically compiled as part of project build.

4. The sketch file should contain two functions i.e., “ setup() ” and
“ loop() ” – the first one is only executed once and the second one
is executed inside an infinite loop.

Let us create our first sketch file:

void setup() {

pinMode(13, OUTPUT);

void loop() {

digitalWrite(13, HIGH);
delay(1000);

digitalWrite(13, LOW);

delay(1000);

The setup() function provides a place for initialization code or pins’


setup; here we are only setting the digital pin numbered 13 as an output pin.
The pin numbers are as per board’s description as printed on Arduino
boards as well.
The loop() is where the actual blinking logic is implemented. First the
digital pin which has already been set in output mode, is set to the HIGH
state thereby switching on an LED attached to it. After that, a delay of 1
second is given to pause the execution before switching off the LED in the
next line of code. The last delay is required to stop the execution for 1
second, otherwise the loop function will exit immediately and will be run
again by the backend code.
Splitting into multiple files
Now, in the interest of inculcating management of code early in the
process of development, let us split our code into multiple files.
Arduino IDE, on opening a sketch file also detects other files in the
same directory and opens them as well. During the build process, other
code files are also compiled as part of the solution.
Although, we can use “ .c ” files as well; however, they are compiled as
C language’s source files. This can cause linking problems during
project build – these are resolvable; but is is advisable that “ .cpp ”
extension may be used to compile the source files as C++ sources –
without dealing with linking issues.
Let us create two files in the same folder as our sketch file is in – our
header file Blink.h contains function declarations for being used in sketch
file:

#ifndef _BLINK_H_

#define _BLINK_H_

void set_pin_mode();

void blink_once();

#endif

Function definitions go in our newly created Blink.cpp file:

#include <Arduino.h>

void set_pin_mode() {

pinMode(13, OUTPUT);

}
void blink_once() {

digitalWrite(13, HIGH);

delay(1000);

digitalWrite(13, LOW);

delay(1000);

As our “ .cpp ” file uses Arduino library’s functions, it also includes


Arduino.h , rest everything is quite self-explanatory. Therefore, to use our
functions, sketch file is simplified:

#include "Blink.h"

void setup() {

set_pin_mode();

void loop() {
blink_once();

Only including Blink.h is required in the sketch file, rest the build system
will do. At the end of this, the folder now contains our sketch file, Blink.h
and Blink.cpp :

Figure 67. Arduino sketch with other source files

Once new source files are placed in the folder along-with the sketch file,
closing and reopening of sketch makes the newly added files discovered
by the IDE if they are not discovered earlier.
After creating the project, board selection and uploading to the target are
performed just as normally done – there is no special changes in this
case.
Chapter 7:
Example Projects

A rduino projects – or
generally boil down
any other hardware projects for that matter,
to just taking inputs, processing them, and
producing results as outputs. Even though, development environment for
Arduino – in terms of both, the hardware kits and application development
environment is meant to be simple; though by “simple” many people
consider its simplicity only in terms of just creating simple projects –
though by being simple, it primarily relates with the ease with which
projects can be developed, as for the most part it very much allows creating
complex projects. Afterall, you are only limited by the limits of the
microcontroller chip you are using, not the application development
environment.
The majority of modern microcontrollers available these days are quite
capable of executing millions of instructions per second – even the simpler
ones. Once we consider this aspect and compare their capabilities with the
majority of personal computers in use just a few years back, we can easily
appreciate the computer power that these microcontrollers have. However,
power consumption remains a considerable factor in many applications,
whereby the devices are battery powered and are required to operate in
remote locations for longer periods of time – that’s when power
conservation must be taken seriously. Therefore, modern microcontrollers
allow several power saving modes. Whereas such requirements are for
special purposes and the default programming style of Arduino
environment i.e., running in a single infinite loop does not stop from
prototyping the solutions. That is why we will not be covering this aspect as
it varies with different microcontrollers. Therefore, in the next sections we
will focus on the development and interfacing aspects utilizing the default
environment – and that is also relevant for majority of development tasks.
Interfacing a keypad
Taking inputs from keypads is quite a common task in applications
where simple user interfaces are required.
Intel’s initial microcontrollers found remarkable success in being used
inside keyboards.
To save on the number of lines keys in keypads are arranged in rows and
columns. This scheme of connection saves lines by having only rows and
columns connected with the microcontroller instead of reserving single
input for each key. For example, 16 keys can be arranged in four rows and
four columns, thereby we only require eight microcontroller lines to read
the state of sixteen keys. For illustration:

Figure 68. 4x4 Keypad


For reading the keypad state, scanning is done – whereby, each row is
given a signal one by one, and if a key is pressed in that row, then the same
signal is made available on corresponding column. For example, if key “10”
is pressed which belongs to third row and second column, this implies that
when it is pressed then applying voltages on third row will appear on
second column, not on any other column.
For our sample keypad, we will use the following 16-keys keypad:

Figure 69. 16-keys Keypad

This keypad is quite commonly available, and its rows and columns are
as indicated on the figure with R and C. For us it is enough to clarify the
concept of interfacing it; additionally, the same scheme can be used for
reading the key states in other keypads as well.

To determine the row and column lines of keypad, a multimeter can


easily be used in diode or resistance measurement mode as pressing a
key connects its corresponding row and column together.
For our sample code below, it is assumed that the keypad’s connection
with digital pins of Arduino is in sequence i.e., Rows (1 to 4) are connected
with Digital Pins (9 to 6) and Columns (1 to 4) with Digital Pins (5 to 2) in
sequence – rest it is clearly defined in code.
To keep the code general and reusable in other projects and other types
of keypads, we are declaring a class in KeypadReader.h inside cblk
namespace.

namespace cblk {

typedef void(*keypad_callback_t)(char);

class KeypadReader {

private:

keypad_callback_t onKeyDown = nullptr;

keypad_callback_t onKeyUp = nullptr;


static const unsigned char timePerRow_us = 2;

unsigned char rows;

unsigned char cols;

const unsigned char* rowPins;

const unsigned char* colPins;

const char* keyMap;

char* keysStatus;

public:

KeypadReader(keypad_callback_t on_key_down,

keypad_callback_t on_key_up,

const unsigned char num_rows,

const unsigned char num_cols,

const unsigned char* row_pins,


const unsigned char* col_pins,

const char* key_map);

void update();

};

}
First a callback function pointer type is defined – the aim is to give two
function pointers to the constructor of the class which stores the pointers
inside private variables. The functions take a character as an input, and the
aim is to call one function when a key is pressed and the other one when the
key is released – both are passed in the character value of the pressed or
released key.
To keep the code generic enough, the constructor is also given total
number of rows and columns of the keypad and their connected pins, lastly
an array of face values of keys is also given so that the face values are
passed to callback functions when corresponding key is pressed or released.
In the end, the update function is the one which checks the key presses
whenever invoked.
The implementation goes in KeypadReader.cpp :

#include <Arduino.h>

#include "KeypadReader.h"

First for accessing the types in KeypadReader.h and Arduino library, we are
including them.

#define INVOKE_KEY_DOWN_HANDLER(r, c) if (onKeyDown != NULL) \

onKeyDown( \

keyMap[r * cols + c]);

#define INVOKE_KEY_UP_HANDLER(r, c) if (onKeyUp != NULL) \


onKeyUp( \

keyMap[r * cols + c]);

These macros are defined to help in invoking the corresponding callback


functions by passing in corresponding face values from provided keymap,
though only invoking when function pointers are provided during class
initialization.

#define SET_ALL_ROW_PINS_HIGH for (unsigned char __r = 0; \

__r < rows; \

__r++) { \

digitalWrite(rowPins[__r], HIGH); \

}
This macro helps in setting all the row pins in high state. In our reading
scheme, we are internally pulling up all the columns and setting each row to
low one by one to read keys.

#define IS_KEY_STATUS_CHANGED(r,c,val) keysStatus[r * cols + c] \

!= val

#define SET_KEY_STATUS(r,c,val) keysStatus[r * cols + c] \

= val

These are defined to help in checking whether the specified key press
state has changed or not.

#define KEY_UNPRESSED_SYMBOL HIGH

#define KEY_PRESSED_SYMBOL LOW

Here we have only defined our convention of tracking if a key is pressed


or not.

cblk::KeypadReader::KeypadReader(

keypad_callback_t on_key_down,

keypad_callback_t on_key_up,

const unsigned char num_rows,

const unsigned char num_cols,


const unsigned char* row_pins,

const unsigned char* col_pins,

const char* key_map) {

onKeyDown = on_key_down;

onKeyUp = on_key_up;

rows = num_rows;

cols = num_cols;

rowPins = row_pins;

colPins = col_pins;

keyMap = key_map;

keysStatus = new char[rows * cols];


for (unsigned char r = 0; r < rows; r++) {

pinMode(rowPins[r], OUTPUT);

for (unsigned char c = 0; c < cols; c++) {

pinMode(colPins[c], INPUT_PULLUP);

for (unsigned char r = 0; r < rows; r++) {

for (unsigned char c = 0; c < cols; c++) {

keysStatus[r * cols + c] = KEY_UNPRESSED_SYMBOL;

}
The constructor is simple, we are only initializing the values and initial
states of buffer to later track changes.
Now the final update part plays the key role of checking key presses and
releases.

void cblk::KeypadReader::update() {

for (unsigned char r = 0; r < rows; r++) {

SET_ALL_ROW_PINS_HIGH;

digitalWrite(rowPins[r], LOW);

delayMicroseconds(timePerRow_us);

for (unsigned char c = 0; c < cols; c++) {

unsigned char val = digitalRead(colPins[c]);

if (IS_KEY_STATUS_CHANGED(r, c, val)) {

if (val == KEY_PRESSED_SYMBOL) {

INVOKE_KEY_DOWN_HANDLER(r, c);

} else {

INVOKE_KEY_UP_HANDLER(r, c);
}

SET_KEY_STATUS(r, c, val);

}
Simply, during each iteration only corresponding pin is pulled low and
after giving some settling time, we read state of each column and when any
key is found pressed or released, its corresponding callback function is
invoked.
As a final glue part – the sketch file only uses this functionality:

#include "KeypadReader.h"

using namespace cblk;

Here we are only pulling in the keypad reading functionality.

// ------------------------

// Keypad Events

// ------------------------

void on_key_press(char key) {

Serial.print(key);

Serial.println('+');

void on_key_release(char key) {


Serial.print(key);

Serial.println('-');

These are our two callback functions – one for being called when a key
is pressed and the other when the key is released. Both are quite simple;
they transmit the key’s face value over the serial port and append “+” or “-”
to indicate pressed or released state respectively.

// ------------------------

// Defining the Keypad

// ------------------------

constexpr unsigned char rows = 4;

constexpr unsigned char cols = 4;

constexpr unsigned char rowPins[rows] = {9, 8, 7, 6};

constexpr unsigned char colPins[cols] = {5, 4, 3, 2};


constexpr char keyMap[] = {

// Col-1 Col-2 Col-3 Col-4

'1', '2', '3', 'A', // Row-1

'4', '5', '6', 'B', // Row-2

'7', '8', '9', 'C', // Row-3

'*', '0', '#', 'D' // Row-4

};

// the object to interact with the keypad

KeypadReader keypad(on_key_press,

on_key_release,

rows, cols,

rowPins, colPins,

keyMap);

Now you can see that we have kept the code in class generic enough to
be used with other keypads as well. Here we are defining and giving the
number of rows, number of columns, connection pins, face values of keys
and callback functions to our class constructor.

void setup() {

Serial.begin(115200);

void loop() {

keypad.update();

With all the background logic in place, now leaves us to only setup
serial port for communication in setup function as our callbacks transmit
key presses and depresses over the serial port and the loop function only
invokes our class’s update method which in turn checks key presses and
depresses, and on specified state, calls the callback methods.
Upon uploading the solution to an Arduino device, we can receive the
key presses using any serial monitor or you can use the serial monitor
packaged with Arduino IDE.

Figure 70. Serial Monitor displaying key presses and depresses

While using the Serial Monitor, remember to use the same Serial Port
settings as the ones used in Arduino application – inside the setup()
function.
Cyclic task execution
A lot of microcontroller-based applications can be broken down into
separate tasks with each responsible for performing a certain set of
activities. For example, a simple application that reads two sensor values,
displays on attached display device, and transmits over the serial port – it
can be split into three tasks i.e., reading sensor values, updating the display,
and transmitting over the serial port. The tasks-based approach can make
the application design cleaner and extensible for example, extending the
application to also receive keypad inputs for changing the units in which the
values are being displayed on screen.
Another important improvement that tasks-based approach provides is
running individual tasks at different rates e.g., reading sensor values and
displaying them on screen after every
30 milliseconds can be configured for better real-time visibility, whereas
transmitting over the serial port can be configured to execute after every
second to reduce communication overheads. Although, this is just an
example to illustrate a use-case scenario, for every application use-case
decision of task execution frequency can be decided based on target
requirements.
The master-loop approach i.e., writing code in functions and calling
them in an indefinite loop makes it difficult to run individual tasks at
different rates; it becomes further complicated when task execution
frequencies are required to change dynamically. When code is indefinitely
executed in a master-loop, it simply executes as fast as the microcontroller
can execute instructions based on clock frequency; therefore, to slow down
the execution rates mostly delays are used. Though, within that delay the
microcontroller can execute many instructions.
Let us assume that our microcontroller can execute a million instructions
per second; this implies that a delay of one millisecond is enough time
for the microcontroller to execute
one thousand instructions.
The proper approach for tasks-based execution would be using a Real-
time Operating System that can preempt a running task to execute another
higher priority task which is ready to execute. Though, for our simpler
environment, especially when hard real-time requirements are not required
to be met, we can use Arduino library’s function millis() that returns the
number of milliseconds since starting the microcontroller and use it as our
timescale.
An important thing while breaking the tasks into distinct functions is
that they should not contain indefinitely running loops otherwise the
whole execution can stop as we are not using task preemption in this
case.
Let us start by declaring our class in CyclicTaskExecutor.h that keeps track
of execution of its associated task function:

namespace cblk {

typedef void(*func_ptr_t)();

class CyclicTaskExecutor {

private:

func_ptr_t funcPtr;

unsigned int callingInterval_ms;

unsigned long lastCalledTime_ms;

public:
CyclicTaskExecutor(func_ptr_t func_ptr,

unsigned int calling_interval_ms,

bool call_on_zero = false);

void change_interval(unsigned int calling_interval_ms,

bool call_on_zero = false);

void update(unsigned long current_time_ms);

};

It is a simple declaration of our class that stores the task function, its
interval on which to invoke the function and the last timestamp it was
invoked. In addition, it has a public method to change the interval and an
update method that takes in the value taken from millis() function and
decides whether to run the task or not.
The implementation goes like:

#include <Arduino.h>
#include "CyclicTaskExecutor.h"

#define MAX_MILLIS (unsigned long)0xffffffff


The maximum value is defined to keep track of value overflow, it is
only representing the maximum value an unsigned long can hold.

cblk::CyclicTaskExecutor::CyclicTaskExecutor(

func_ptr_t func_ptr,

unsigned int calling_interval_ms,

bool call_on_zero) {

funcPtr = func_ptr;

change_interval(calling_interval_ms, call_on_zero);

The constructor simply stores the function pointer internally and passes
the rest of the parameters by treating the construction itself as a change of
interval.

void cblk::CyclicTaskExecutor::change_interval(

unsigned int calling_interval_ms,

bool call_on_zero = false) {

callingInterval_ms = calling_interval_ms;

if (call_on_zero) {

if (funcPtr != NULL) funcPtr();

}
lastCalledTime_ms = millis();

Here we can make sense of other constructor parameters i.e., the calling
interval is just stored into a private variable, and when the passed in
Boolean is true it means to invoke the function at the time the interval is
updated, otherwise it would be invoked periodically at the specified
interval.

void cblk::CyclicTaskExecutor::update(unsigned long current_time_ms) {

unsigned long elapsedTime_ms = 0;

if (current_time_ms < lastCalledTime_ms) {

elapsedTime_ms = (MAX_MILLIS - lastCalledTime_ms) + current_time_ms;

} else {

elapsedTime_ms = current_time_ms - lastCalledTime_ms;

if (elapsedTime_ms >= callingInterval_ms) {

lastCalledTime_ms = current_time_ms;

if (funcPtr != NULL) funcPtr();


}

The update function is simply calculating the time difference between


current time and the last time its associated function was invoked; thereby,
if the time for invocation has reached it simply invokes the specified
function and stores the last invocation time for next invocation calculation.
Therefore, we can simply use this class in Arduino sketch:

#include "CyclicTaskExecutor.h"

using namespace cblk;

CyclicTaskExecutor* exe_1;

CyclicTaskExecutor* exe_2;

void task_1() {

Serial.prisnt(".");

}
void task_2() {

Serial.print("|");

We have defined the tasks and the pointers of executor class against
each task.
The tasks should not block execution with infinite loops otherwise the
whole application would halt.

void setup() {

Serial.begin(115200);

exe_1 = new CyclicTaskExecutor(task_1, 1000);

exe_2 = new CyclicTaskExecutor(task_2, 5000);

void loop() {

unsigned long currentTime_ms = millis();

exe_1->update(currentTime_ms);
exe_2->update(currentTime_ms);

}
Thus, we can simply create the executor objects and update them inside
the main loop.
Output on Serial Monitor shows that task_1 is executed five times more
than task_2 :

Figure 71. Serial data received by executing task executor example

This concept can be extended to include additional tasks, whereby the


execution rate of each can be controlled independently. Though, real-time
constrains are not considered, but it is still quite helpful in many scenarios.
Way-forward
We can go on and on creating different projects with Arduino; however,
the aim is not to make you an expert in creating certain number of
predefined projects; the important thing is to get you going on by yourself,
exploring different libraries available online and through Arduino
ecosystem – by yourself. What we have covered here is the main building
blocks and using C++ constructs in building projects and understanding the
basics can make your journey enjoyable by understanding what is going on
under the hoods.
Further you can interface distinct types of displays, sensors, and motors
through already available libraries, samples, and your own code. Though,
you might need to add additional hardware depending upon power
requirements of target devices – online help is available in terms of sample
circuits, that you can understand as they include either basic electronic
devices or even pre-packaged driver circuits. The important thing is not to
burn your microcontroller by drawing too much of drive current.

Typically, microcontrollers can supply 20 milliamperes through their


digital ports; therefore, do consult your specific microcontroller
documentation and use resistors, transistors, Solid-state Relays, or other
pre-packaged driver circuits to avoid draining excessive current while
controlling external devices.

You might also like