Datatypes in C
Datatypes in C
AuthorKarl Söderby
Last revision08/02/2024
The Arduino platform has since its start in 2005, grown to become one of the most
recognizable brands in the space of electronics and embedded design.
But what are the cornerstones of Arduino? What is a "board", how do I write code to it, and
what are the tools needed to create my own project? The goal with this guide is to provide
you with an overview to the Arduino project.
In this guide, you will gain an overview of the Arduino Ecosystem, and a set of links leading
to more detailed articles.
Overview
This guide is divided into four main sections: hardware, software tools, Arduino API, and
Quick Reference. You can navigate to each of these sections directly through the links below:
Arduino Hardware
In this section, we will dedicate some time to learn about some fundamentals in electronics,
and about the basic operation of an Arduino board.
Main Parts
Program Structure
The "Sketch"
Example Sketch
Libraries
Core Specific API
Arduino Software Tools
In this section you will learn how to set up your development environment as well as learning
about what options there are.
A Typical Workflow
Arduino IDE 1.8.x
Arduino IDE 2
Arduino Cloud
Web Editor
Library Manager
Arduino CLI
Quick Reference
The quick reference is an extract from the full Arduino API, containing popular functions,
structures and methods.
General
Serial Communication
GPIO / Pin Management
Structure
Arduino Hardware
Over the years, Arduino has released hundreds of hardware designs in many shapes and
forms.
1. Microcontroller - this is the brain of an Arduino, and is the component that we load
programs into. Think of it as a tiny computer, designed to execute only a specific number of
things.
2. USB port - used to connect your Arduino board to a computer.
3. USB to Serial chip - the USB to Serial is an important component, as it helps translating
data that comes from e.g. a computer to the on-board microcontroller. This is what makes it
possible to program the Arduino board from your computer.
4. Digital pins - pins that use digital logic (0,1 or LOW/HIGH). Commonly used for switches
and to turn on/off an LED.
5. Analog pins - pins that can read analog values in a 10 bit resolution (0-1023).
6. 5V / 3.3V pins- these pins are used to power external components.
7. GND - also known as ground, negative or simply -, is used to complete a circuit, where the
electrical level is at 0 volt.
8. VIN - stands for Voltage In, where you can connect external power supplies.
Depending on the Arduino board, you will find many more components. The items listed
above are generally found on any Arduino board.
Basic Operation
Most Arduino boards are designed to have a single program running on the microcontroller.
This program can be designed to perform one single action, such as blinking an LED. It can
also be designed to execute hundreds of actions in a cycle. The scope varies from one
program to another.
The program that is loaded to the microcontroller will start execution as soon as it is
powered. Every program has a function called "loop". Inside the loop function, you can for
example:
Read a sensor.
Turn on a light.
Check whether a condition is met.
All of the above.
The speed of a program is incredibly fast, unless we tell it to slow down. It depends on the
size of the program and how long it takes for the microcontroller to execute it, but it is
generally in microseconds (one millionth of a second).
Circuit Basics
Circuits consist of at least one active electronic component, and a conductive material, such
as wires, so that current can pass through. When working with an Arduino, you will in most
cases build a circuit for your project.
A simple example of a circuit, is an LED circuit. A wire is connected from a pin on the
Arduino, to an LED via a resistor (to protect the LED from high current), and finally to the
ground pin (GND). When the pin is set to a HIGH state, the microcontroller on the Arduino
board will allow an electric current to flow through the circuit, which turns on the LED.
When the pin is set to a LOW state, the LED will turn off, as an electric current is not flowing
through the circuit.
Circuits are typically represented as schematics, which are the blueprints for your circuit. The
image below shows the schematic's representation of the same circuit shown in the image
above.
Schematics of a circuit.
Schematics of a circuit.
Electronic Signals
All communication between electronic components are facilitated by electronic signals.
There are two main types of electronic signals: analog & digital.
Analog Signal
Basics of an analog signal.
Basics of an analog signal.
An analog signal is generally bound to a range. In an Arduino, that range is typically 0-5V, or
0-3.3V.
If we for example use a potentiometer (an analog component used to change the resistance of
a circuit), we can manually adjust this range (0-5V). In the program, this is represented in a
range of 0-1023, which is a 10-bit resolution.
If we write an analog signal using Pulse-Width Modulation (PWM), we can use a range
between 0-255, as we are using an 8-bit resolution.
Digital Signal
Basics of a digital signal.
Basics of a digital signal.
A digital signal works a bit different, representing only two binary states (0 or 1) that are read
as high or low states in the program. This is the most common signal type in modern
technology.
You can easily read and write digital signals on an Arduino, which is useful to for example
read button states, or to turn something on or off.
Digital signals might seem very basic (just 0 or 1), but are actually way more advanced. For
example, we can create a sequence by sending a high or low state rapidly a number of times.
This is known as a binary sequence or a bitstream.
COPY
101101
101110001110011
Which in decimal format is:
COPY
45
23667
This is a clever way of sending large amounts of data from one point to the other, by rapidly
sending high & low signals. In order to interpret the data from the signals, we use Serial
Communication Protocols.
What Is a Sensor?
A sensor, in simple terms, is used to sense its environment, meaning it records a physical
parameter, for example temperature, and converts it into an electronic signal.
Sensors can also take the form of just a simple button: when a state changes (we pressed a
button), the electronic signal is switched from low to high (0 to 1).
There are many types of sensors, and several ways of recording data from them. Perhaps the
easiest to use is an analog sensor, where we communicate a range of values through altering
the voltage input fed into an Arduino analog pin (usually between 0-5 volts). This simply
gives you a range between 0-1023 (a 10-bit resolution).
Digital sensors are a bit more advanced, depending on the type. They rely on Serial
Communication Protocols to send the data accordingly, and requires a bit more effort to
translate the data. As mentioned in the Electronic Signals section above, data is sent using a
binary sequence (e.g. 101101 is 45), and this needs to be addressed and configured on a
software level. Luckily, a lot of sensors are accompanied by software libraries, which makes
it a lot easier to read.
In many cases using a library, all we need is just one line of code:
COPY
sensorValue = sensor.read();
What Is an Actuator?
An actuator, in simple terms, is used to actuate or change a physical state. Some examples
are:
COPY
Explain
Explain
digitalWrite(LED, HIGH); //turn on an LED
digitalWrite(LED, LOW); //turn off an LED
A basic example of this is a button and an LED. We can write a conditional that checks if a
button is pressed, turn on the LED, and turn it off if the button is not pressed. In an Arduino
program, it looks like this:
COPY
Explain
Explain
int buttonState = digitalRead(buttonPin); //read and store the button state (0 or 1)
The SPI and I²C protocols are used for communication between both internal and external
components. The communication is handled by something called a serial bus, which is
attached to a specific pin on the Arduino.
Using the I²C protocol, we can connect several sensors on the same pin, and retrieve the data
accurately. Each device has an address that we need to specify in the program, which we use
when making data requests.
Find out more in the Arduino SPI Guide and Arduino I2C Guide.
Memory
The "standard" Arduino typically has two memories: SRAM and Flash memory.
The SRAM (Static Random-Access Memory) is used to for example store the value of a
variable (such as the state of a boolean). When powered off, this memory resets.
The Flash memory is primarily used to store the main program, or the instructions for the
microcontroller. This memory is not erased when powered off so that the instructions for the
microcontroller are executed as soon as the board is powered.
How much memory is available on an Arduino varies from board to board. For example the
Arduino UNO has a 32kB flash / 2kB SRAM, while a Nano 33 IoT has 256kB flash / 32kB
SRAM. You will find this information in each of the product's documentation pages, which
are available in the Arduino Hardware Documentation.
To learn more about memory on an Arduino, visit the Arduino Memory Guide.
Embedded Sensors
An IMU (Inertial Measurement Unit) on the Nano RP2040 Connect board.
An IMU (Inertial Measurement Unit) on the Nano RP2040 Connect board.
Many new Arduino boards come equipped with embedded sensors. For example, the Nano 33
BLE Sense has 7 embedded sensors, but is only 45x18mm (the size of a thumb). These are all
connected via the I²C protocol as mentioned above, and has a unique address.
The u-blox NINA-W102 Wi-Fi / Bluetooth® module on the Nano RP2040 Connect board.
The u-blox NINA-W102 Wi-Fi / Bluetooth® module on the Nano RP2040 Connect board.
The most popular and inexpensive modules are the Wi-Fi & Bluetooth® modules. The Wi-Fi
modules allow your board to connect to routers, and to request and send data over the
Internet. In a way, it works the same as your computer when requesting various types of data
over the Internet, just in a smaller scale.
Bluetooth® is used to communicate with nearby devices, and is really useful for maintaining
a fast and reliable connection. For example, in real-life applications, Bluetooth® technology
for example used in wireless headphones & speakers.
Similarly to serial protocols, radio modules use their own set of protocols to communicate,
such as HTTP, MQTT and UPD.
Arduino API
Visit the Arduino Language Reference to explore the full Arduino API.
The Arduino API, aka the "Arduino Programming Language", consists of several functions,
variables and structures based on the C/C++ language.
Main Parts
The Arduino API can be divided into three main parts: functions, variables and structure:
Functions: for controlling the Arduino board and performing computations. For example, to
read or write a state to a digital pin, map a value or use serial communication.
Variables: the Arduino constants, data types and conversions. E.g. int, boolean, array.
Structure: the elements of the Arduino (C++) code, such as
sketch (loop(), setup())
control structure (if, else, while, for)
arithmetic operators (multiplication, addition, subtraction)
comparison operators, such as == (equal to), != (not equal to), > (greater than).
The Arduino API can be described as a simplification of the C++ programming language,
with a lot of additions for controlling the Arduino hardware.
Program Structure
The absolute minimum requirement of an Arduino program is the use of two functions: void
setup() and void loop(). The "void" indicates that nothing is returned on execution.
void setup() - this function executes only once, when the Arduino is powered on. Here we
define things such as the mode of a pin (input or output), the baud rate of serial
communication or the initialization of a library.
void loop() - this is where we write the code that we want to execute over and over again,
such as turning on/off a lamp based on an input, or to conduct a sensor reading every X
second.
The above functions are always required in an Arduino sketch, but you are of course able to
add several more functions, which is useful for longer programs.
The "Sketch"
In the Arduino project, a program is referred to as a "sketch". A sketch is a file that you write
your program inside. It has the .ino extension, and is always stored in a folder of the same
name.
The folder can include other files, such as a header file, that can be included in your sketch.
Example Sketch
Below is an example of a standard Arduino sketch, which contains some popular Arduino
programming elements.
COPY
Explain
Explain
/*
This is a comment at the top of a program,
it will not be recognized as code. Very good
to add an explanation of what your code does
here.
void loop() {
sensorValue = analogRead(sensorPin); // do a sensor reading
Libraries simplifies the use of otherwise complex code, such as reading a specific sensor,
controlling a motor or connecting to the Internet. Instead of having to write all of this code
yourself, you can just install a library, include it at the top of your code, and use any of the
available functionalities of it. All Arduino libraries are open source and free to use by
anyone.
To use a library, you need to include it at the top of your code, as the example below:
COPY
#include <Library.h>
Most libraries also have a set of examples that are useful to get started with the library.
You can browse through all official and contributed libraries in the Arduino Libraries page.
For example, the classic ArduinoCore-avr package, automatically includes the EEPROM, and
SoftwareSerial libraries, and can be used freely without any additional installation. In this
package you will find the classic Arduino UNO, Nano, Mega2560 and more.
PDM - used for sampling audio from microphones found onboard the Nano 33 BLE Sense
and Nano RP2040 Connect.
Ethernet - for using the Ethernet functionalities of the Portenta Vision Shield.
GSM - to access GSM functionalities on the Portenta Cat. M1/NB IoT GNSS Shield.
These features are documented in the documentation landing page of each product. A list of
all hardware can be found at docs.arduino.cc.
Another integral part of the Arduino ecosystem are its software tools.
In order to program your board, you need to write a program, compile that program into
machine code, and finally: send over the new program to your board.
The Arduino IDE facilitates all this, from the first line of code written, to have it executed on
the Arduino board's microcontroller. It is a program, or application, that you can download
(or use an online version), to manage all of your code development. Back in the day, this was
a complicated process, that required a good set of knowledge in electronics & computer
science. Now, anyone can learn how to do it, with the help of the Arduino IDE.
1. Install your board - this means installing the right "package" for your board. Without the
package, you can simply not use your board. Installing is done directly in the IDE, and is a
quick and easy operation.
2. Create a new sketch - a sketch is your main program file. Here we write a set of
instructions we want to execute on the microcontroller.
3. Compile your sketch - the code we write is not exactly how it looks like when uploaded to
our Arduino: compiling code means that we check it for errors, and convert it into a binary
file (1s and 0s). If something fails, you will get this in the error console.
4. Upload your sketch - once the compilation is successful, the code can be uploaded to your
board. In this step, we connect the board to the computer physically, and select the right serial
port.
5. Serial Monitor (optional) - for most Arduino projects, it is important to know what's going
on on your board. The Serial Monitor tool available in all IDEs allow for data to be sent from
your board to your computer.
For what is now considered the "legacy" editor, the Arduino IDE 1.8.X, or "Java IDE", is the
editor that was first released back when Arduino started.
Learn more by visiting the Arduino IDE 1 documentation.
Arduino IDE 2
The new Arduino IDE.
The new Arduino IDE.
In 2021, the Arduino IDE 2 was released. The new IDE has the same functionality, but also
supports features such as auto-completion and debugging.
Web Editor
The Web Editor.
The Web Editor.
The Arduino Web Editor is an online IDE, part of the Arduino Cloud suite. Similar in
function, this editor is completely web based, with online storage among other features. To
use the Web Editor, you will need to register an Arduino account.
Arduino Cloud
The Arduino Cloud.
The Arduino Cloud.
The Arduino Cloud allows you to configure, program and control/monitor your devices - all
in one web based application. With the use of things, or your "digital twin", you can control
and monitor variables directly from dashboards. The service also supports webhooks and
integrations with other services, such as Amazon Alexa.
The Cloud is made for anyone to use, and it does not require much previous experience to get
started.
Get started by reading the Getting Started with the Arduino Cloud guide, or visit the full
documentation.
Library Manager
Library manager on IDE 1.8.x and IDE 2
Library manager on IDE 1.8.x and IDE 2
Every version of the IDE has a library manager for installing Arduino software libraries.
Thousands of libraries, both official and contributed libraries, are available for direct
download. Code examples for each library is made available on download.
To explore all available Arduino libraries, visit the Arduino Libraries page.
Arduino CLI
The Arduino CLI (Command Line Interface).
The Arduino CLI (Command Line Interface).
The Arduino CLI is a command line tool that can be used to compile and upload code to your
board. It has no visual UI, but is very useful for automation. It is designed for more advanced
users.
A proper use of the CLI can speed up your development time by far, as any operation is
executed much faster than in the regular IDE.
Quick Reference
In this section, you will find a list of some of the most common elements in the standard
Arduino API. This will help you get familiar with some key building blocks.
To explore the whole Arduino API, please refer to the Arduino Language Reference, an in-
depth wiki maintained by Arduino and its community. You will find hundreds of entries,
accompanied by code examples and elaborate descriptions.
General
setup()
The setup() function is where you make program configurations.
COPY
void setup() {
//program configurations here
}
loop()
The loop() function is where your main program is stored. It will run as long as your board is
powered.
COPY
void loop() {
//main program here
}
delay()
The delay() function pauses the program for a set number of milliseconds.
COPY
Explain
Explain
void loop() {
}
The delay() function is an incredibly useful function, and you will find it in almost all
examples. But, for efficiency of the code, it is not the best option, as it prevents the Arduino
from doing anything for the duration of the delay.
millis()
The millis() function is a bit more advanced, but an incredibly resourceful function. It allows
you to have multiple events happening simultaneously, without pausing the program. This is
done by measuring time (in milliseconds) passed since the program started.
Then, with the use of intervals and continuously storing the time for last event, a simple
algorithm can be made to have events happening at specific times without pausing the
program.
COPY
Explain
Explain
unsigned long previousMillis_1 = 0; //store time for first event
unsigned long previousMillis_2 = 0; //store time for second event
void setup(){
void loop() {
//conditional that checks whether 1 second has passed since last event
if (currentMillis - previousMillis_1 >= interval_1) {
previousMillis_1 = millis();
//execute a piece of code, every *1 second*
}
//conditional that checks whether 2 seconds have passed since last event
if (currentMillis - previousMillis_2 >= interval_2) {
previousMillis_2 = millis();
//execute a piece of code, every *2 seconds*
}
}
While the millis() function is a more advanced concept than the delay() function, it is a good
to start practicing it early on.
Functions
Learn more about Arduino functions.
You can create custom functions that either just executes code and returns to the program, or
that returns a result.
Explain
Explain
int x;
void loop(){
thisFunction(); //execute the function
}
void thisFunction() {
x++; //increase x by 1 each time function is run.
}
Example of a type int function that returns a value.
COPY
Explain
Explain
int value;
void setup(){
void loop(){
value = returnFunction();
}
int returnFunction() {
int returnValue = 5 + 2;
return returnValue;
}
Variable Definition
Variables can either be created locally or globally. Variables that are defined in the loop() are
considered local, and variables defined at the top of your sketch are considered global.
COPY
Explain
Explain
int sensorReading = x; //global variable
void setup(){
void loop(){
int sensorReading = x; //local variable
}
Data Types
See all data types in the Language Reference.
There are several data types available for use, and below are some of the most common:
COPY
Explain
Explain
bool
byte
char
double
float
int
long
short
String
To store data in for example an int (integer):
COPY
int exampleNumber = 25;
For numbers with a lot of decimals, we can use float:
COPY
float exampleNumber = 22.2123002;
Or to store a string, we can use the String function:
COPY
String exampleSentence = "This is a string!";
For simple switches and true/false, we use booleans:
COPY
bool exampleSwitch = true; // true/false
Serial Communication
Read more about the Serial class.
Serial.begin()
Initializes serial communication between board & computer. This is defined in the void
setup() function, where you also specify baud rate (speed of communication).
COPY
void setup() {
Serial.begin(9600);
}
Serial.print()
Prints data to the serial port, which can be viewed in the Arduino IDE Serial Monitor tool.
COPY
void loop() {
Serial.print();
}
Serial.read()
Reads the incoming serial data.
COPY
void loop() {
int incomingByte = Serial.read();
}
GPIO / Pin Management
Configuring, controlling and reading the state of a digital/analog pin on an Arduino.
pinMode()
Configures a digital pin to behave as an input or output. Is configured inside the void setup()
function.
COPY
pinMode(pin, INPUT); //configures pin as an input
pinMode(pin, OUTPUT); //configures pin as an output
pinMode(pin, INPUT_PULLUP); //enables the internal pull-up resistor
You can read more about digital pins in the article about Digital Pins.
digitalRead()
Reads the state of a digital pin. Used to for example detect a button click.
COPY
int state = digitalRead(pin); //store the state in the "state" variable
digitalWrite()
Writes a high or low state to a digital pin. Used to switch on or off a component.
COPY
digitalWrite(pin, HIGH); // writes a high (1) state to a pin (aka turn it on)
digitalWrite(pin, LOW); // writes a low (0) state to a pin (aka turn it off)
analogRead()
Reads the voltage of an analog pin, and returns a value between 0-1023 (10-bit resolution).
Used to read analog components.
COPY
sensorValue = analogRead(A1); //stores reading of A1 in "sensorValue" variable
analogWrite()
Writes a value between 0-255 (8-bit resolution). Used for dimming lights or setting the speed
of a motor. Also referred to as PWM, or Pulse Width Modulation.
COPY
analogWrite(pin, value); //write a range between 0-255 to a specific pin
PWM is only available on specific pins (marked with a "~" symbol).
Structure
The structure of the Arduino API is based on C++, and can be considered the building blocks
of a program.
Conditionals
Conditionals are some of the most popular used elements in any program. In Arduino, a
typical conditional consists of an if and else statement.
COPY
Explain
Explain
if(variable == true){
//do something
}
else {
//do something else
}
You can make use of several if/else statements in your code.
Loops / Iterations
The for and while loops are commonly used in programs, to execute a block of code for a set
number of times, or while a condition is met.
A basic use of a while loop to execute a block of code while variable is true.
COPY
while (variable == true) {
//do something
}
A basic use of a for loop is to execute a block of code a custom number of times (in this case,
10).
COPY
for (int x = 0; x < 10; x++) {
//do something 10 times
}
To break out of a loop, you can use break. In the snippet below, if a condition is met (variable
is true), we break out of the loop.
COPY
Explain
Explain
for (int x = 0; x <= 10; x++) {
if(variable == true) {
break;
}
}
Arithmetic Operators
Arithmetic operators are used for addition, subtraction, multiplication, division and other
mathematical calculations.
COPY
Explain
Explain
int x = 5;
int y = 2;
x + y; //result is 7
x * y; //result is 10
x - y; //result is 3
Comparison Operators
Comparison operators are used for comparing one property to another, and are a key
component of a conditional statement.
COPY
Explain
Explain
!= //not equal to
< //less than
<= //less than or equal to
== //equal to
> //greater than
>= //greater than or equal to
To use them in a conditional, see the following example:
COPY
if(value > 10) {
//do something
}
Boolean Operators
Boolean operators (logical NOT !, AND && and OR ||) can for example be used for more
advanced conditionals.
COPY
if(value > 10 && otherValue > 10){
//do something if only if *both* conditions are met
}
To use the OR || operator:
COPY
if(value > 10 || otherValue > 10){
//do something if a one *or* the other condition is met
}
To use the NOT ! operator:
COPY
if(!value){
//do something if value is false (!)
}
Compound Operators
Compound operators consists of two operators, which are used to perform two operations in
the same statement. This can for example be to add + and assign = a value at the same time.
COPY
Explain
Explain
x = 5;
y = 2;
Language Reference
Arduino programming language can be divided in three main parts: functions, values
(variables and constants), and structure.
Functions
For controlling the Arduino board and performing computations.
Digital I/O
digitalRead()
digitalWrite()
pinMode()
Analog I/O
analogRead()
analogReadResolution()
analogReference()
analogWrite()
analogWriteResolution()
Advanced I/O
noTone()
pulseIn()
pulseInLong()
shiftIn()
shiftOut()
tone()
Time
delay()
delayMicroseconds()
micros()
millis()
Math
abs()
constrain()
map()
max()
min()
pow()
sq()
sqrt()
Trigonometry
cos()
sin()
tan()
Characters
isAlpha()
isAlphaNumeric()
isAscii()
isControl()
isDigit()
isGraph()
isHexadecimalDigit()
isLowerCase()
isPrintable()
isPunct()
isSpace()
isUpperCase()
isWhitespace()
Random Numbers
random()
randomSeed()
Bits and Bytes
bit()
bitClear()
bitRead()
bitSet()
bitWrite()
highByte()
lowByte()
External Interrupts
attachInterrupt()
detachInterrupt()
digitalPinToInterrupt()
Interrupts
interrupts()
noInterrupts()
Communication
Print
Serial
SPI
Stream
Wire
USB
Keyboard
Mouse
Variables
Arduino data types and constants.
Constants
Floating Point Constants
HIGH | LOW
INPUT | INPUT_PULLUP | OUTPUT
Integer Constants
LED_BUILTIN
true | false
Conversion
(unsigned int)
(unsigned long)
byte()
char()
float()
int()
long()
word()
Data Types
array
bool
boolean
byte
char
double
float
int
long
short
size_t
string
String()
unsigned char
unsigned int
unsigned long
void
word
Variable Scope & Qualifiers
const
scope
static
volatile
Utilities
PROGMEM
sizeof()
Structure
The elements of Arduino (C++) code.
Sketch
loop()
setup()
Control Structure
break
continue
do...while
else
for
goto
if
return
switch...case
while
Further Syntax
#define (define)
#include (include)
/* */ (block comment)
// (single line comment)
; (semicolon)
{} (curly braces)
Arithmetic Operators
% (remainder)
* (multiplication)
+ (addition)
- (subtraction)
/ (division)
= (assignment operator)
Comparison Operators
!= (not equal to)
< (less than)
<= (less than or equal to)
== (equal to)
> (greater than)
>= (greater than or equal to)
Boolean Operators
! (logical not)
&& (logical and)
|| (logical or)
Pointer Access Operators
& (reference operator)
* (dereference operator)
Bitwise Operators
& (bitwise and)
<< (bitshift left)
>> (bitshift right)
^ (bitwise xor)
| (bitwise or)
~ (bitwise not)
Compound Operators
%= (compound remainder)
&= (compound bitwise and)
*= (compound multiplication)
++ (increment)
+= (compound addition)
-- (decrement)
-= (compound subtraction)
/= (compound division)
^= (compound bitwise xor)
|= (compound bitwise or)
AuthorLiam Aljundi
Last revision08/02/2024
The Arduino Web Editor allows you to write code and upload sketches to any official
Arduino board directly from your web browser (Chrome, Firefox, Safari and Edge).
However, we recommend you use Google Chrome.
The Arduino Web Editor is hosted online, therefore it is always be up-to-date with the latest
features and support for new boards.This IDE lets you write code and save it to the Cloud,
always backing it up and making it accessible from any device. It automatically recognizes
any Arduino and Genuino board connected to your PC, and configures itself accordingly.
All you need to get started is an Arduino account. The following steps can guide you to start
using the Arduino Web Editor:
2. Create a new Arduino Account at this link. Complete the registration form, then hit the
"create account" button. Then you will receive an email with a link to activate your account.
Select the link and a new page will open with your confirmed account information.
Your Sketchbook: a collection of all your sketches (a sketch is a program you upload on your
board).
Examples: read-only sketches that demonstrate all the basic Arduino commands (built-in tab),
and the behavior of your libraries (from the libraries tab).
Libraries: packages that can be included to your sketch to provide extra functionalities.
Serial monitor: a feature that enables you to monitor, receive and send data to and from your
board via the USB cable.
Help: helpful links and a glossary about Arduino terms.
Preferences: options to customize the look and behavior of your editor, such as text size and
color theme.
2. The second column views the content of the chosen option.
3. The third column, the code area, is the one you will use the most. Here, you can write
code, verify it and upload it to your boards, save your sketches on the Cloud, and share them
with anyone you want.
Now that you are all set up, let’s try to make your board blink!
1. Connect your Arduino or Genuino board to your computer. Boards and serial ports are
auto-discovered and selectable in a single dropdown. Pick the Arduino/Genuino board you
want to upload to from the list at the top of the third column.
2. Let’s try an example: Choose Examples on the menu on the left (first column), then Basic
and Blink. The Blink sketch is now displayed in the code area.
Finding an example
Finding an example
3. To upload it to your board, press the "Upload" button near the dropdown menu. While the
code is verifying and uploading, a "BUSY" label replaces the upload button. If the upload is
successful, the message "Success: done uploading" will appear in the bottom output area.
4. Once the upload is complete, you should see on your board the yellow LED with an L next
to it start blinking. You can adjust the speed of blinking by changing the delay number in the
parenthesis to 100, and upload the Blink sketch again. Now the LED should blink much
faster.
Congratulations! You have successfully programmed your board to blink its on-board LED!
You can find more information about the Arduino Web Editor here.
AuthorLiam Aljundi
Last revision08/02/2024
The Arduino environment can be extended through the use of libraries. Just like most
programming platforms, libraries provide extra functionality for use in sketches, e.g. working
with hardware or manipulating data. To use a library in a sketch, select it from Sketch >
Import Library.
Offline IDE
A number of libraries come installed with the IDE, but you can also download or create your
own. Here are some instructions for setting up a library on the offline IDE:
1. Open the IDE and click "Sketch" on the menu tab and then Include Library > Manage
Libraries.
2. Search for the library that you need, click on it, then select the version of the library you
want to install.
3. Finally, click on install and wait for the IDE to install the new library.
Once it has finished, an Installed tag should appear next to the Bridge library, so you can go
ahead and close the library manager.
Now the new library will be available in the Sketch > Include Library menu. If you want to
add your own library to Library Manager, follow these instructions.
Online IDE
The process of setting up libraries on the online IDE (Arduino Web Editor) is quite similar to
the offline one:
1. Login to the Arduino Web Editor.
2. Open the "Libraries" tab from the left menu, and search for libraries. The list displays read-
only libraries, authored and maintained by the Arduino team and its partners.
3. When you find the library, you can add it to your sketch by selecting the "Include" button.
You can also see the related examples, and select a specific version, if available.
Adding a library
Adding a library
4. If you can't find a specific library on the list, you can search every existing library through
the library manager. From there you also have the option to add them to your favorites list by
clicking on the star next to the library you want. Once you star a library, you can view it
under the "favorites" tab and use its examples (if available).
AuthorLiam Aljundi
Last revision08/02/2024
Using the Arduino Cloud
With the Arduino Cloud desktop or mobile platform, you can quickly connect, manage and
monitor your devices from anywhere in the world.
Arduino Cloud allows you to automatically create any code to program your device with -
just add a couple of lines to customize it how you want. If you’re new to Arduino don’t worry
there’s example code for hundreds of sensors and actuators.
The following steps will guide you to start using the Arduino Cloud:
2. Check if you have a cloud compatible board. The picture below shows all official Arduino
boards that are compatible.
Note: The MKR GSM 1400 and MKR NB 1500 require a SIM card to connect to the Cloud,
as they communicate over the mobile networks. The MKR WAN 1300 and 1310 board
requires a Arduino PRO Gateway LoRa to connect to the Cloud.
4. Access the Arduino Cloud from any page on arduino.cc by clicking on the bento menu (9-
dots) on the top right corner, or you can go directly to the Arduino Cloud.
Creating a Thing
1. The user journey always begins by creating a new Thing. In the Thing overview, we can
choose what device to use, what Wi-Fi network we want to connect to, and create variables
that we can monitor and control.
2. Next we need to add a device by clicking on the "Select device" button on the Thing
overview. Here, we choose from any board that we have already been configured, or select
the Configure new device option.
3. Now we can add our first variable by clicking on the Add variable button. We can choose
name, data type, update the setting and interaction mode for our variable. There are several
data types we can choose from, such as int, float, boolean, long, char. There’s also special
variables, such as Temperature, Velocity, and Luminance that can be used. The variables we
create are automatically generated into a sketch file.
4. Finally, we need to connect to a Wi-Fi network by simply clicking the Configure button in
the network section. Enter your network credentials and click Save. This information will
also be generated into your sketch file!
A special sketch file can now be found in the Sketch tab, which includes all of the
configurations that you have made. When the sketch has been uploaded, it will work as a
regular sketch, but it will also update the Cloud variables that we use!
Additionally, each time we create a variable that has the Interaction Mode enabled, a function
will also be generated. Every time this variable is triggered from the Cloud, it will execute the
code within this function! This means that we can leave most of the code out of the loop()
and only run code when needed.
When we are happy with our sketch, we can upload it to our board, by clicking the upload
button.
After we have successfully uploaded the code, we can open the Serial Monitor tab to view
information regarding our connection. If it is successful, it will print connected to
network_name and connected to cloud.
If it fails to connect, it will print the errors here as well. Now that we have configured the
device & network, created variables, completed the sketch and successfully uploaded the
code, we can move on to the fun part, the dashboard!
Creating the dashboard
IoT CLoud Dashboards
IoT CLoud Dashboards
Dashboards are visual user interfaces for interacting with your boards over the Cloud, and we
can set up many different setups depending on what your IoT project needs.
We can access our dashboards by clicking on the Dashboards tab at the top of the Arduino
Cloud interface, where we can create new dashboards, and see a list of dashboards created for
other Things.
Navigating to dashboards.
Navigating to dashboards.
If we click on Create new dashboard, we enter a dashboard editor. Here, we can create
something called widgets. Widgets are the visual representation of our variables we create,
and there are many different ones to choose from. Below is an example using several types of
widgets.
When we create widgets, we also need to link them to our variables. This is done by clicking
on a widget we create, selecting a Thing, and selecting a variable that we want to link.
Once it is linked, we can either interact with it, for example a button, or we can monitor a
value from a sensor. As long as our board is connected to the Cloud, the values will update
automatically!
Congratulations! Now you are ready to create your own IoT system. You can find more
information about the Arduino Cloud here.
Network Configuration
Note: that the Arduino Cloud operates with different domains and ports, which means that if
we want devices working with the Arduino Cloud, they need to be allowed access to certain
domains through your firewall.
If you are connected to your school or university networks, please provide your admin with
the following instructions:
Domain Port
mqtts-up.iot.arduino.cc 8884
mqtts-sa.iot.arduino.cc 8883
wss.iot.arduino.cc 8443
2. Provide NTP access to time.arduino.cc, note that the NTP port for time.arduino.cc is 123
UDP.
If you are having issues connecting to the Arduino Cloud through your home network, follow
these instructions:
AuthorLiam Aljundi
Last revision08/02/2024
There are many factors involved in uploading a program to your Arduino board, and if any of
them are missing, the upload could fail.
You can check the following suggestions to help you solve any potential problem:
1. Make sure that you chose the right board and port, and have installed all the drivers
needed.
2. If you are still running into an error, you can copy the error message and search it through
our troubleshooting guide page.
3. If you cannot find any help through our troubleshooting guide page, you can use our forum
support for help.
Make sure you have the right item selected in the Tools > Board menu. If you have an
Arduino UNO, you'll need to choose it.
Then, check that the proper port is selected in the Tools > Serial Port menu (if your port
doesn't appear, try restarting the IDE with the board connected to the computer):
On the Mac, the serial port should be something like /dev/tty.usbmodem621 (for the UNO or
Mega 2560) or /dev/tty.usbserial-A02f8e (for older, FTDI-based boards).
On Linux, it should be /dev/ttyACM0 or similar (for the UNO or Mega 2560) or
/dev/ttyUSB0 or similar (for older boards).
For Windows, it will be a COM port, but you'll need to check in the Device Manager (under
Ports) to see which one. If you don't seem to have a serial port for your Arduino board, see
the following information about drivers.
Drivers
Drivers provide a way for the software on your computer (i.e. the Arduino software) to talk to
any hardware you connect to it (i.e. the Arduino board).
The easiest way to check if the drivers for your board are installed correctly is by opening the
Tools > Serial Port menu in the Arduino software with the Arduino board connected to your
computer.
Additional menu items should appear relative to when you open the menu without the
Arduino connected to your computer. Note that it shouldn't matter what name the Arduino
board's serial port gets assigned as long as that's the one you pick from the menu.
On Windows 7 (particularly the 64-bit version), you might need to go into the Device
Manager and update the drivers for the UNO or Mega 2560. Just right click on the device (the
board should be connected to your computer), and point Windows at the appropriate .inf file
again. The .inf is in the drivers/ directory of the Arduino software (not in the FTDI USB
Drivers sub-directory of it).
If you get this error when installing the UNO or Mega 2560 drivers on Windows XP: "The
system cannot find the file specified", you might try this suggestion (about adding a
"RunOnce" key to "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\
CurrentVersion").
On Linux, the UNO and Mega 2560 show up as devices of the form /dev/ttyACM0. These are
not supported by the standard version of the RXTX library that the Arduino software uses for
serial communication. The Arduino software download for Linux includes a version of the
RXTX library patched to also search for these /dev/ttyACM* devices. There's also an Ubuntu
package (for 11.04) which includes support for these devices. If, however, you're using the
RXTX package from your distribution, you may need to symlink from /dev/ttyACM0 to
/dev/ttyUSB0 (for example) so that the serial port appears in the Arduino software.
Run:
COPY
sudo usermod -a -G tty yourUserName
sudo usermod -a -G dialout yourUserName
Log off and log on again for the changes to take effect.
Troubleshooting Guide
In the Arduino Help Center you will find articles on frequently asked questions, and
troubleshooting guides for most of the errors you might encounter. You can use our help
center by browsing through the different categories and the questions provided in each of
them, or by searching your error in the search tab.
Forum Support
If it still doesn't work, you can ask for help in the forum. Please include the following
information:
Last revision08/02/2024
Starting from the Arduino Software (IDE) version 1.6.2, all Arduino AVR boards are
installed by default. Some Arduino boards require an additional core to be installed, therefore
we have implemented the Boards Manager as the preferred tool to add cores to your Arduino
Software (IDE).
Cores are necessary to make new microcontrollers compatible with your Arduino Software
(IDE) and, possibly, the existing sketches and libraries. We develop the cores for the new
microcontrollers used in our new generation boards, but anyone may develop a core for their
own boards following the rules and requirements we have issued.
Boards manager will open and you will see a list of installed and available boards; the
download of the index file could take some time and the list appears at the end of this
process; please be patient.
Click on the Arduino SAM Boards core, choose the version in the drop-down menu and click
on Install.
Core SamD
After installation is complete an Installed tag appears next to the core name. You can close
the Board Manager.
Now you can find the new board in the Tools > Board menu.
Prefs Core
If you have more JSON files to add, click on the little icon on the right of the field and open a
specific window where you may input more URLs, one line at a time.
Core MoreJsons
After this procedure, the new cores will be available for install in the Boards Manager. Please
refer to the information provided by the third party core author to get more specific
instructions.
The text of the Arduino getting started guide is licensed under a Creative Commons
Attribution-ShareAlike 3.0 License. Code samples in the guide are released into the public
domain.
Digital Pins
Discover how digital pins work and how they can be configured.
Last revision08/02/2024
The pins on the Arduino can be configured as either inputs or outputs. This document
explains the functioning of the pins in those modes. While the title of this document refers to
digital pins, it is important to note that vast majority of Arduino (Atmega) analog pins, may
be configured, and used, in exactly the same manner as digital pins.
This also means however, that pins configured as pinMode(pin, INPUT) with nothing
connected to them, or with wires connected to them that are not connected to other circuits,
will report seemingly random changes in pin state, picking up electrical noise from the
environment, or capacitively coupling the state of a nearby pin.
The value of this pullup depends on the microcontroller used. On most AVR-based boards,
the value is guaranteed to be between 20kΩ and 50kΩ. On the Arduino Due, it is between
50kΩ and 150kΩ. For the exact value, consult the datasheet of the microcontroller on your
board.
When connecting a sensor to a pin configured with INPUT_PULLUP, the other end should
be connected to ground. In the case of a simple switch, this causes the pin to read HIGH
when the switch is open, and LOW when the switch is pressed.
The pullup resistors provide enough current to dimly light an LED connected to a pin that has
been configured as an input. If LEDs in a project seem to be working, but very dimly, this is
likely what is going on.
The pullup resistors are controlled by the same registers (internal chip memory locations) that
control whether a pin is HIGH or LOW. Consequently, a pin that is configured to have pullup
resistors turned on when the pin is an INPUT, will have the pin configured as HIGH if the pin
is then switched to an OUTPUT with pinMode(). This works in the other direction as well,
and an output pin that is left in a HIGH state will have the pullup resistors set if switched to
an input with pinMode().
Prior to Arduino 1.0.1, it was possible to configure the internal pull-ups in the following
manner:
COPY
pinMode(pin, INPUT); // set pin to input
digitalWrite(pin, HIGH); // turn on pullup resistors
NOTE: Digital pin 13 is harder to use as a digital input than the other digital pins because it
has an LED and resistor attached to it that's soldered to the board on most boards. If you
enable its internal 20k pull-up resistor, it will hang at around 1.7V instead of the expected 5V
because the onboard LED and series resistor pull the voltage level down, meaning it always
returns LOW. If you must use pin 13 as a digital input, set its pinMode() to INPUT and use
an external pull down resistor.
Short circuits on Arduino pins, or attempting to run high current devices from them, can
damage or destroy the output transistors in the pin, or damage the entire Atmega chip. Often
this will result in a "dead" pin in the microcontroller but the remaining chip will still function
adequately. For this reason it is a good idea to connect OUTPUT pins to other devices with
470Ω or 1k resistors, unless maximum current draw from the pins is required for a particular
application.
See Also
pinMode
digitalWrite
digitalRead
Last revision08/02/2024
A description of the analog input pins on an Arduino chip (ATmega8, ATmega168,
ATmega328P, or ATmega1280).
A/D converter
The ATmega controllers used for the Arduino contain an onboard 6 channel (8 channels on
the Mini and Nano, 16 on the Mega) analog-to-digital (A/D) converter. The converter has 10
bit resolution, returning integers from 0 to 1023. While the main function of the analog pins
for most Arduino users is to read analog sensors, the analog pins also have all the
functionality of general purpose input/output (GPIO) pins (the same as digital pins 0 - 13).
Consequently, if a user needs more general purpose input output pins, and all the analog pins
are not in use, the analog pins may be used for GPIO.
Pin mapping
The analog pins can be used identically to the digital pins, using the aliases A0 (for analog
input 0), A1, etc. For example, the code would look like this to set analog pin 0 to an output,
and to set it HIGH:
COPY
pinMode(A0, OUTPUT);
digitalWrite(A0, HIGH);
Pull-up resistors
The analog pins also have pull-up resistors, which work identically to pull-up resistors on the
digital pins. They are enabled by issuing a command such as
COPY
pinMode(A0, INPUT_PULLUP); // set pull-up on analog pin 0
Be aware however that turning on a pull-up will affect the values reported by analogRead().
The ATmega datasheet also cautions against switching analog pins in close temporal
proximity to making A/D readings (analogRead) on other analog pins. This can cause
electrical noise and introduce jitter in the analog system. It may be desirable, after
manipulating analog pins (in digital mode), to add a short delay before using analogRead() to
read other analog pins.
Last revision08/02/2024
The Fading example demonstrates the use of analog output (PWM) to fade an LED. It is
available in the File->Sketchbook->Examples->Analog menu of the Arduino software.
Pulse Width Modulation, or PWM, is a technique for getting analog results with digital
means. Digital control is used to create a square wave, a signal switched between on and off.
This on-off pattern can simulate voltages in between the full Vcc of the board (e.g., 5 V on
UNO, 3.3 V on a MKR board) and off (0 Volts) by changing the portion of the time the
signal spends on versus the time that the signal spends off. The duration of "on time" is called
the pulse width. To get varying analog values, you change, or modulate, that pulse width. If
you repeat this on-off pattern fast enough with an LED for example, the result is as if the
signal is a steady voltage between 0 and Vcc controlling the brightness of the LED.
In the graphic below, the green lines represent a regular time period. This duration or period
is the inverse of the PWM frequency. In other words, with Arduino's PWM frequency at
about 500Hz, the green lines would measure 2 milliseconds each. A call to analogWrite() is
on a scale of 0 - 255, such that analogWrite(255) requests a 100% duty cycle (always on),
and analogWrite(127) is a 50% duty cycle (on half the time) for example.
On some microcontrollers PWM is only available on selected pins. Please consider the pinout
diagram of your board to find out which ones you can use for PWM. They are denoted with a
tilde sign (~).
Once you get this example running, grab your Arduino and shake it back and forth. What you
are doing here is essentially mapping time across the space. To our eyes, the movement blurs
each LED blink into a line. As the LED fades in and out, those little lines will grow and
shrink in length. Now you are seeing the pulse width.
Debugging Fundamentals
Learn the basics of debugging microcontroller-based systems.
Embedded systems design can be challenging since it combines hardware design, firmware,
and software development, all in one particular device or product. In order to produce high-
quality embedded firmware and software for a particular device or product, debugging is a
necessary step in their development process. Debugging is the process of confirming that, one
by one, many things that we believe to be true and functional in our code are true. We find a
"bug" in our code when one our more assumptions are not valid.
People worldwide have been talking about "bugs" for a long time; even Thomas Alva Edison
used the word back in his days. The word bug has been used as an old term for "monster";
like gremlins in machinery, bugs are malicious.
The following article will discuss different debugging tools and techniques used to find bugs
in microcontroller-based systems, especially those based on Arduino® hardware.
The compiler shows 100 errors: This usually does not mean there are 100 errors; it often gets
thrown off track for a while when the compiler finds an error. The compiler tries to recover
and pick up again after the first error, but sometimes it reports false errors. Only the first error
message is genuinely reliable; try to fix one error at a time and then recompile the program.
Getting weird compiler messages: Compiler errors are shown in terse jargon, sometimes hard
to read and understand, but with hidden and valuable information. Read the error message
carefully; it will always tell where, inside the code, the error occurred.
Traditional Techniques: Trace Code and GPIO's
Adding trace code is probably the simplest and most basic debugging technique used in
embedded systems software design. This method adds trace code to the program to print out
messages or values of variables (using the Serial.print() function, for example) as the
program executes. For example, determining if a particular function is halting or freezing in
our code can be made with trace code as shown in the example code below:
COPY
Explain
// Print a message if the execution gets here
Serial.println("Code got here");
You can pass flash-memory based Strings to Serial.print() instruction by wrapping them with
F(); for example, Serial.println(F("Code got here")) prints a flash-memory based String.
Another trace code technique consists of dumping strategic information into an array at run
time, we can then observe the contents of the array at a later time (for example, when the
program terminates); this technique is also known as dump into array. Assume good and bad
are two strategic variables we want to capture. The first step is to define a debug buffer in
RAM to save the debugging measurements as shown in the code below:
COPY
#define DUMP_BUFFER_SIZE 32
unsigned char goodBuffer[DUMP_BUFFER_SIZE];
unsigned char badBuffer[DUMP_BUFFER_SIZE];
unsigned long count = 0;
The variable count is used to index into the debug buffer, it must be initialized to zero before
the debugging process begins. The code shown below dumps strategic information from the
good and bad variables into the debug buffer:
COPY
Explain
void Save_Debug_Buffer(void) {
if (count < DUMP_BUFFER_SIZE) {
goodBuffer[count] = good;
badBuffer[count] = bad;
count++;
}
}
General Purpose Input/Output (GPIO) pins can help debug purposes when the UART is in
use or adding trace code is not particularly helpful. For example, we can turn on or off the
built-in LED of an Arduino® board by inserting a digitalWrite(LED_BUILTIN, HIGH)
instruction before or after questionable areas in our programs as shown in the example code
below. If the built-in LED turns on, then we know that a particular line of code executed:
COPY
Explain
// Print a message if the execution gets here
Serial.println("Code got here");
// Turn on the built-in LED for one second to indicate that myFunction1 was executed
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
// Turn on the built-in LED for one second to indicate that myFunction2 was executed
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
Remote Debuggers
Remote debugging is another common approach used to debug embedded systems. Remote
debuggers work by connecting a particular embedded system to a host computer and then
using software in the host computer to interact with the embedded system hardware. Remote
debuggers are helpful when the development environment is on a different architecture rather
than the target system. For example, think about developing code on a Windows-based
computer for an ARM-based microcontroller system.
Remote debuggers usually have two essential parts: a front-end debugger and a back-end
debugger.
The front-end debugger contains the user interface (can be graphical or command-line-based)
and offers the programmer choices about the execution of the code in the embedded system
hardware.
The back-end debugger, also known as the "debug monitor," is specific for a particular
processor architecture or family an usually work with an external hardware tool, like an in-
circuit emulator or an in-circuit debugger. It starts when the processor resets and handles the
runtime instruction between the front-end debugger and the embedded system hardware.
The debugger tool is a newly introduced yet less famous feature of Arduino IDE 2. Check out
this tutorial that shows how to use the Arduino® IDE 2 debugger with supported boards.
Simulators
Simulators are tools used to simulate the functionality and the instruction set of the target
processor. These tools, usually, can only simulate the target processor functionalities but not
its environment and external parts and components. Simulators are handy in the early stages
of the development process, where we only have the software but have not implemented any
hardware yet.
Tinkercad Circuits is an excellent simulator for beginners in the Arduino® ecosystem. This
tool can simulate the instruction set of an Arduino® UNO board and the functionalities of
several electronic components such as resistors, LEDs, motors, LCDs, and some sensors.
An in-circuit debugger (or ICD) is also a specialized tool connected between a host computer
and a processor for debugging real-time applications faster and easier; this tool uses some
memory and GPIO pins of the target microcontroller during the debugging operations. With
an ICD, developers access an on-chip debug module which is integrated into the CPU over an
interface (for example, JTAG). This debug module allows developers to load, run, halt and
step the processor.
SEGGER Microcontroller GmbH & Co, CC BY-SA 3.0, via Wikimedia Commons.
The fundamental difference between an ICE and an ICD relies on the resources used to
control the debug target. In ICEs, resources are provided by the emulation hardware; in ICDs,
resources are provided by the target processor.
Arduino® boards with a SAMD microcontroller feature native on-chip debug capabilities;
these debugging capabilities can be used with an external ICD tool over JTAG or SWD
interfaces. CMSIS-DAP compliant debug probes can be used with the Arduino IDE 2 out of
the box without any configuration file; non-standard debug probes require a special
configuration. Check out these tutorials to learn how to use an external ICD tool with SAMD
based Arduino boards and the Arduino IDE 2:
Hardware Tools
Embedded systems developers and typical software developers differ on a key aspect: their
"closeness" to hardware; embedded system developers are usually closer to hardware than
typical software developers. There are several tools that embedded systems developers use to
find out what is going on with the hardware, which is very helpful for low-level software
debugging. These tools are multimeters, logic analyzers, oscilloscopes, and software-defined
radios (SDRs).
Let us take a look at each one of the hardware debugging tools. A basic understanding of
these tools can significantly improve debugging skills, especially while developing embedded
systems.
Multimeters
A digital multimeter (DMM) is a hardware tool that can be used to measure two or more
electrical values, usually voltage (in volts), current (in amps), and resistance (in ohms).
DMMs are great tools and one of the most fundamental pieces of test equipment that can be
used to debug electrical problems within an embedded system.
Digital multimeter.
Digital multimeter.
Logic Analyzers
A logic analyzer is a hardware tool designed specifically for capturing, displaying, and
measuring electrical signals in a digital circuit. This tool consists of several digital inputs pins
capable of detecting whether an electric signal is at a specific logic level (1 or 0). Logic
analyzers are also capable of showing the relationship and timing of different electrical
signals in a digital circuit and often capable also of analyzing digital communication
protocols (for example, SPI communication protocol).
Oscilloscopes
An oscilloscope is a hardware tool that graphically displays electrical signals and shows how
those signals change over time. Signals are measured in an oscilloscope with the help of a
sensor.
Software-Defined Radios
A software-defined radio (SDR) is a radio communication system that uses software for the
modulation and demodulation of radio signals. Traditional radio communications systems
processing relies on hardware components; this limits their reprogramming to a very low
level. SDRs are much more flexible since they can be reconfigured by software.
To get the most out of an oscilloscope and GPIO pins is by measuring its performance, this
means to determine a signal's electrical and timing properties. For example, an unnecessary
delay in the code can be identified with this information:
COPY
Explain
void myFunction() {
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("Code got here");
count++;
digitalWrite(LED_BUILTIN, LOW);
}
myFunction() execution duration can be measured by setting a GPIO pin to be driven to a
high logic level when its execution begins; when myFunction() execution ends, the GPIO pin
can be driven to a low logic level. An oscilloscope can then provide information if the
function execution took precisely the defined time, longer or shorter than expected, or if it
has any unaccounted electrical behavior that changes the expected behavior.
Now let us talk about wireless communications. Wireless communications are a key feature
in developing new Internet of Things (IoT) devices with different requirements or
specifications and for different purposes. Wireless communication is present on many
embedded systems, and Arduino® hardware is no exception for this feature. The question
now is: how do we debug wireless communications between devices?
A simple and common technique used to debug wireless communications between two or
more devices consists of using acknowledge flags. Using acknowledge flags aids in
understanding the device's behavior when communication is established between them by
providing their current status. This process is also found on physical communication
protocols, such as I2C or SPI. Because of the different protocol types in wireless
communications, using acknowledge flags as a debugging method can be applied but differ in
their own rules. The easiest way to confirm that the data exchange was successful is to check
the data log on each end device. Hardware tools mentioned earlier (DMMs, oscilloscopes,
and logic analyzers) can be used to provide more in-depth details and add more value to this
type of debugging technique.
However, not everything is connected on a physical layer, but on an abstract layer. This
abstract layer in wireless communications is where electromagnetic waves propagate through
space. Why would we need to debug electromagnetic waves propagating through space?
Sometimes, we need to verify that the wireless transceiver configuration of a particular
embedded system is correct, for example, its transmission power. SDRs can be helpful in this
situation: SDRs can be used as cheap spectrum analyzers. A spectrum analyzer is a hardware
tool that measures the magnitude of a signal versus the frequency; this tool is mainly used to
measure a signal's power spectrum. Using SDRs as spectrum analyzers is usually an optional
debugging method in the development process, ensuring a more robust system design.
Several programs can be used with SDRs; GQRX is one of the most popular software;
GQRX is open-source and with cross-platform support (Linux and macOS). AirSpy and
CubicSDR are other popular software used with SDRs and cross-platform support (Windows,
Linux, and macOS).
Shown visual representation of the signal via SDR software can now be used to verify the
transmission power outputted by the device and the amount of data transmitted. This will
help visualize the device's wireless communication configuration properties. It will be
possible to verify the transmission and reception power, the number of bytes transmitted, and
the frequency it is supposed to transmit. These properties can be debugged through the
frequency spectrum and refined to provide edge wireless communication performance on
embedded systems.
COPY
Explain
/*
Program:
- Debugging_techniques_example.ino
Description:
- This example combines data from the LSM9DS1 accelerometer, gyroscope, and
magnetometer into a single code. On top of it,
it helps to understand different methods and techniques of debugging when the code
structure combines multiple tasks.
The circuit:
- Arduino Nano 33 BLE Sense.
Code based on examples created by Riccardo Rizzo, Jose García, and Benjamin Dannegård.
Modified by Taddy Ho Chung and José Bagur (16/02/22).
*/
#include <Arduino_LSM9DS1.h>
#define DUMP_BUFFER_SIZE 32
unsigned char GoodBuffer[DUMP_BUFFER_SIZE];
unsigned char BadBuffer[DUMP_BUFFER_SIZE];
unsigned long count = 0;
uint8_t good, bad = 0;
float x, y, z, ledvalue;
int degreesX = 0, degreesY = 0;
int plusThreshold = 30, minusThreshold = -30;
void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println("- Started");
if (!IMU.begin()) {
Serial.println("- Failed to initialize IMU!");
bad++;
save_bebug_buffer();
disp_debug_buffer();
debug_stop();
}
accelermeter_setup();
gyroscope_setup();
void loop() {
for (int i = 0; i < 5; i++) {
accelerometer_task();
gyroscope_task();
magnetometer_task();
}
save_debug_buffer();
debug_stop();
}
// Accelerometer setup
void accelermeter_setup() {
Serial.print(F("- Accelerometer sample rate: "));
Serial.print(IMU.accelerationSampleRate());
Serial.println(F(" Hz"));
}
if (x > 0.1) {
x = 100 * x;
degreesX = map(x, 0, 97, 0, 90);
Serial.print(F("- Tilting up "));
Serial.print(degreesX);
Serial.println(F(" degrees"));
}
if (x < -0.1) {
x = 100 * x;
degreesX = map(x, 0, -100, 0, 90);
Serial.print(F("- Tilting down "));
Serial.print(degreesX);
Serial.println(F(" degrees"));
}
if (y > 0.1) {
y = 100 * y;
degreesY = map(y, 0, 97, 0, 90);
Serial.print(F("- Tilting left "));
Serial.print(degreesY);
Serial.println(F(" degrees"));
}
if (y < -0.1) {
y = 100 * y;
degreesY = map(y, 0, -100, 0, 90);
Serial.print(F("- Tilting right "));
Serial.print(degreesY);
Serial.println(F(" degrees"));
}
delay(1000);
}
// Gyroscope setup
void gyroscope_setup() {
Serial.print(F("- Gyroscope sample rate = "));
Serial.print(IMU.gyroscopeSampleRate());
Serial.println(F(" Hz"));
Serial.println();
Serial.println(F("- Gyroscope in degrees/second"));
}
if(x < 0) {
ledvalue = -(x);
}
else {
ledvalue = x;
}
analogWrite(LED_BUILTIN, ledvalue);
delay(500);
}
void debug_stop() {
Serial.flush();
exit(1);
}
As shown, the complete code unifies the accelerometer, gyroscope, and magnetometer into a
single code structure. As it involves tasks from different modules, it is separated into different
functions and executed in a more identifiable manner. The code includes a trace code
technique for debugging (dump into an array) to understand precisely how the code operates.
The good and bad marks are located at the points of interest in the code and will dump into
assigned arrays to be able to display at the end of the code.
It is crucial to know when to stop the code from debugging. While the code shown above can
be debugged at runtime, it is much easier to know when to stop or how to stop the code
operation. For instance, stopping within the first run gives the following result:
In the Arduino Serial Monitor, we can observe that it has a 1 good mark and a 1 bad mark.
The good mark came from the gyroscope having ready the data for use, while the bad mark
came from the accelerometer as the data was not ready. So it is possible to see that the
accelerometer does not have enough time to get the data ready before it gets to the
measurement task. We can try by running a certain number of instances before it gets to the
array dump sector, and the result can be seen as follows:
The accelerometer performed its task without any issue except the first runtime instance,
resulting in 9 good marks but 1 bad mark due to this behavior. The Serial.println(F())
instruction of module setups and task runtimes also shows us if the code could get past the
operations without any issue. By this, it is possible to know the code structure does not
misbehave, but for the first time when the device is starting, the accelerometer requires more
time to get the data ready in the first instance.
COPY
Explain
void loop() {
for (int i = 0; i < 5; i++) {
digitalWrite(12, LOW);
accelerometer_task();
gyroscope_task();
magnetometer_task();
digitalWrite(12, HIGH);
}
save_debug_buffer();
debug_stop();
}
Using an oscillocope and a GPIO to measure the time it takes to complete tasks.
Using an oscillocope and a GPIO to measure the time it takes to complete tasks.
Final Thoughts about Debugging
Debugging is a necessary step for developing robust and reliable embedded systems software.
We can end this article by mentioning the four most essential phases of debugging stated by
Robin Knoke in this article about debugging embedded C that was published in the
Embedded Systems Programming magazine:
Testing: this phase exercises the capability of the embedded software by stimulating it with a
wide range of input values and in different environments.
Stabilization: this phase attempt to control the conditions that generate a specific bug.
Localization: this phase involves narrowing the range of possibilities until the bug can be
isolated to a specific code segment in the embedded software.
Correction: this phase involves eradicating the bug from the software.
Knowing the potential causes of bugs allows us to adopt strategies that minimize their
occurrence. Many different debugging techniques and external devices are present to aid this
process. Maybe some software designs do not require the usage of external debuggers, for
example. However, when the software involves different requirements, especially scalability,
things change drastically for the development process. The debugging techniques and the
external debuggers will support this development process, thus granting sophisticated
software. In most cases, we will know how the device will behave with the software, its
computational performance, and even achieve non-power hungry devices due to clean
memory management.
Debugging may be an overlooked aspect in developing embedded systems, but it is its most
serious yet crucial tool. If we desire to develop robust and reliable embedded systems, the
debugging process should consistently be implemented to achieve these goals.
Do you want to improve your debugging and engineering skills? A highly recommended
reading is Debugging: The 9 Indispensable Rules for Finding Even the Most Elusive
Software and Hardware Problems by David J. Agans.
Do you want to learn more about digital multimeters? Learn more about them in this article
from Fluke®.
Do you want to learn more about oscilloscopes? Learn more about them in this article from
Tektronix®.
Do you want to learn more about logic analyzers? Learn more about them in this article from
Saleae®.
Do you want to learn more about spectrum analyzers? Learn more about them in this article
from Tektronix®.
Do you want to learn more about SDRs? Check out the Great Scott Gadgets video series
about SDRs. The video series from Great Scott Gadgets is a complete course about SDRs.
You will learn the fundamentals of Digital Signal Processing (DSP) and build flexible SDR
applications using GNU Radio.
References
[1] P. Koopman, Better Embedded System Software. S.L.: Drumnadrochit Press, 2010.
[2] D. J. Agans, Debugging: The Nine Indispensable Rules for Finding Even the Most
Elusive Software and Hardware Problems. New York: Amacom, 2002.
[3] M. Barr and A. Massa, Programming Embedded Systems: with C and GNU Development
Tools. Johanneshov: MTM, 2013.
[4] J. W. Valvano, Embedded Systems: Introduction to ARM® Cortex™-M
Microcontrollers. United States: Self-published, 2015
3V3 and 5V are standard voltage levels nowadays in power supplies. Although there is only a
1V7 difference between both voltages, it is enough to provide a significant difference in
power efficiency. This guide will show you why 3V3 is the current standard voltage level for
power supplies in electronic circuits and devices and some general tips when designing and
handling these voltage levels in your circuits or devices powered by Arduino.
Reducing the power supply voltage yields an exponential decrease in power consumption.
A standard exists for defining the voltage levels of input and output voltages for every power
supply voltage level; this standard was developed by The Joint Electron Device Engineering
Council (JEDEC); it is the JEDEC Standard 8-A for LV interface levels. The JEDEC
Standard 8-A for LV interface levels is described in the image shown below for 3V3 and 5V
logic families:
VOL: maximum output voltage level an electronic device will provide for a LOW signal
VIL: maximum input voltage level an electronic device will still considered as a LOW signal
Vt: threshold voltage at which an electronic device switches its interpretation of a signal from
low to high or vice versa
VIH: minimum input voltage level an electronic device will still considered as a HIGH
signal.
VOH: minimum output voltage level an electronic device will still considered as a HIGH
signal
For more in-depth information about current microelectronic standards, please look at the
JEDEC website.
Color coding power lines makes it much easier to identify the voltage and Ground (GND)
lines. According to industry regulations and standards, red is typically used to indicate a
voltage line, while black is used to indicate a GND line; colors vary depending on the
regulation or standard.
For more information on electrical regulations and standards, please check out the National
Electrical Safety Code® from the Institute of Electrical and Electronics Engineers (IEEE).
Fuse Integration
One easy way to protect electronic devices is to integrate an onboard power protection circuit
into the device. Several circuit designs can be implemented to achieve this; one of the easiest
circuits to implement is using a fuse.
Fuse integration to electronic devices is not a complicated design process. The following
schematic shows a simple reverse polarity protection circuit that can be used in low-power
DC circuits:
This simple reverse polarity protection circuit uses a fuse and a diode that later connects to
the electronic circuit, which can be referred to as the load.
The key points of the circuit presented above are the Transient Voltage Suppressor diode and
the MOSFET of P-Channel type. This protection circuit will help you save the Protected
Load and to have it as a good reference for protection design. Although due to its electric
components, it becomes a little more advanced to cover in the scope of this guide.
If you are interested in reading more, visit this article by Mehdi Sadaghdar.
So for this matter, how do we protect the system? The solution can be based of the proper
Reverse Polarity Protection showed previously. The proper Polarity Reverse Polarith
Protection implements a bidirectional Transient Voltage Suppressor while adding the P-
Channel MOSFET with a zenerdiode and two resistors to get all its flavours.
To give quick explanation on Transient Voltage Suppressor - It is a type of a diode that helps
to protect high-spike voltages generated at the output of Power Supply.
But a simple Reverse Polarity Protection, with a Transient Voltage Suppressor diode can be
used to protect the over-voltage and over-current issues. If you want to go further into
protecting the Load from over-voltage and over-current, it is possible to integrate Surge
Stopper to provide active protection. May increase the cost, however it is a good measure to
protect the Load.
We will use a bidirectional Logic Level Converter to step the voltage level, to be able to use
sensors or logics at higher or lower voltage levels. This is an option to use if tight electric
specification is implemented on the board.
The bidirectional Logic Level Converter used can be found here (SparkFun).
A voltage divider is the simplest yet easy to implement solution. It uses 2 resistors to create a
lower voltage output. So, knowing the input voltage and targeted output voltage and a
reference resistor, it is simple enough to calculate the other required resistor to implement to
produce desired voltage. Below is a voltage dividing circuit.
Voltage/Resistive Divider
Voltage/Resistive Divider
When using this circuit, you will still need to be cautious of the residing capacitance that is
connected at the output of this circuit and with the quick rise times, as for certain applications
with cautious timing requirements or modules non-response to quick rise times will be
affected.
Stepping Up
To step up voltage, you will need to use a little bit more constructive electric circuit by using
diodes. The following circuit shows how to use diodes to step up voltage.
You will need to biase the diodes with precaution and the resistor that is much lower than the
input impedance of the 5V gate. One of the know-hows shared by Microchip is to use
Schottky diodes to gain slight high-level voltage and reduce low-level voltage from
incrementing. Following circuit uses a different setup.
Step Up Circuit - MOSFET
Step Up Circuit - MOSFET
This circuit uses the MOSFET as a switch and takes the 5V logic from the drain. It is useful if
the logic inversion can be treated, as 3.3V logic becomes inverted. To begin with MOSFET, a
2N7000 or a BSS138 MOSFET can be used for this circuit.
You can use the Bi-Directional Logic Level Shifter from SparkFun to test and also for
deployment if the requirements enables its integration. The advantage of this particular shifter
is that it provides 4 channels to shift within the voltage references given. High Voltage level
and Low Voltage level references are injected with desired voltage level and channels are
used to transmit the data in between.
The circuit above uses the bi-directional logic shifter to establish I2C interface with any
sensor capable of the protocol. The SCL and SDA lines go through a High Voltage channel
and establishes communication with the sensor that is connected at its respective Low
Voltage Channel.
The configuration of the Logic Shifter usually does not change, as the the purpose is to
transmit the signal from a High to a Low Level or vice-versa, depending on the architecture
operation. Thus, the previous schematic illustrates usual global connection configuration. As
it can be to interface the Arduino board with another computing module working on a
different voltage level. Below schematic shows the specific of each channel and focus the
scope inside the schematic symbol box of the Logic Shifter.
Each channel is composed by two resistors and a MOSFET that will use the reference High
and Low voltages to transfer the signal from the respective module.
If you want to know about some know-hows from Microchip, you can read Microchip: 3V
Tips 'n Tricks to learn about wide variety of techniques used with 3.3V and 5V levels.
Level Shifting the voltage has its own science dedicated to it and Philips Semiconductor
welcomes you if you are ready learn deeper about Bi-Directional Level Shifter for I2C Bus
and Other Systems with their Application Note AN97055.
References
[1] Larsson, E. (2006). Introduction to Advanced System-on-Chip Test Design and
Optimization. Springer Publishing.
[2] Kularatna, N. (2018). DC Power Supplies Power Management and Surge Protection for
Power Electronic Systems. Amsterdam University Press.
[3] Ballan, H., & Declercq, M. (2010). High Voltage Devices and Circuits in Standard CMOS
Technologies. Springer Publishing
Arduino API
A reference to the Arduino Programming Language.
AuthorKarl Söderby
Last revision08/02/2024
Compact version of the Arduino Language Reference. This document is a TLDR; of the
Arduino API.
Functions
Digital I/O
Method & Parameters Description Returns
int digitalRead(int pin) Reads the state of a digital pin. int
void digitalWrite(int pin, int state) Writes a state to a digital pin. Nothing
void pinMode(int pin, int mode)* Define the mode of a pin. Nothing
*Available modes are:
INPUT (0)
OUTPUT (1)
INPUT_PULLUP (2)
INPUT_PULLDOWN (3)
OUTPUT_OPENDRAIN (4)
Analog I/O
Method & Parameters Description Returns
int analogRead(int pin) Reads the value of an analog pin in a 10-bit resolution (0-
1023).* int
void analogReadResolution(int resolution) Sets ADC read resolution in bits. Nothing
void analogReference(int reference) Changes the voltage reference for a board.**Nothing
void analogWrite(int pin, int value) Writes a value to a PWM supported pin in a 8-bit
resolution (0-255).** Nothing
void analogWriteResolution(int resolution) Sets write resolution for a board. Nothing
*The value range changes based on the resolution. 0-1023 is 10-bit resolution, 0-4096 is 12-
bit and so on.
**Each board/architecture has a set of different reference voltages available.
***The value range changes based on the resolution. 0-255 is default (8-bit).
Advanced I/O
Method & Parameters Description Returns
void tone(int pin, int frequency, long duration) Generates a square wave on specified
pin, with 50% duty cycle. Nothing
void noTone(int pin) Stops generation of square wave on the specified pin. Nothing
long pulseIn(int pin, int state, long timeout) Reads a pulse (either HIGH or LOW) on a pin
and returns the length of the pulse (in microseconds) long
long pulseInLong(int pin, int state, long timeout) Returns the length of the pulse (in
microseconds) long
int shiftIn(int pin, int clockPin, int bitOrder)* Shifts in a byte of data one bit at a time,
and returns the value of the bit read. byte
void shiftOut(int pin, int clockPin, int bitOrder, byte value)** Shifts out a byte of data
one bit at a time. Nothing
*The bitOrder parameter is either MSBFIRST (1) or LSBFIRST (0) (most / least significant
bits).
**The pin used for shiftOut() needs to be configured as an OUTPUT, using pinMode()
Time
Method & Parameters Description Returns
void delay(long milliseconds)Freezes program execution for specified number of
milliseconds. Nothing
void delayMicroseconds(int microseconds) Freezes program execution for specified number
of microseconds. Nothing
long millis() Returns milliseconds passed since program start. long
long micros() Returns microseconds passed since program start. long
Math
Method & Parameters Description Returns
int abs(int value) Calculates the absolute value of a number. int
int constrain(int value, int min, int max) Constrains a number to be within a range. int
long map(long val, long min, long max, long newMin, long newMax) Re-maps a number
from one range to another. long
int max(int val1, int val2) Returns the greater of two values. int
int min(int val1, int val2) Returns the smaller of two values. int
double pow(double base, double exponent) Raises a base to the power of an exponent.
double
int sq(int value) Calculates the square of a number. int
double sqrt(double value) Calculates the square root of a number. double
Trigonometry
Method & Parameters Description Returns
cos(double angle) Calculates the cosine of an angle in radians. double
sin(double angle) Calculates the sine of an angle in radians. double
tan(double angle) Calculates the tangent of an angle in radians.double
Characters
Method & Parameters Description Returns
boolean isAlpha(char c) Checks if the character is an alphabetic character. boolean
boolean isAlphaNumeric(char c) Checks if the character is an alphanumeric character.
boolean
boolean isAscii(char c) Checks if the character is a 7-bit ASCII character. boolean
boolean isControl(char c) Checks if the character is a control character. boolean
boolean isDigit(char c) Checks if the character is a digit (0-9). boolean
boolean isGraph(char c) Checks if the character is a printable character, excluding
space. boolean
boolean isHexadecimalDigit(char c) Checks if the character is a hexadecimal digit (0-9, A-F,
a-f). boolean
boolean isLowerCase(char c) Checks if the character is a lowercase alphabetic character.
boolean
boolean isPrintable(char c) Checks if the character is a printable character, including space.
boolean
boolean isPunct(char c) Checks if the character is a punctuation character. boolean
boolean isSpace(char c) Checks if the character is a whitespace character. boolean
boolean isUpperCase(char c) Checks if the character is an uppercase alphabetic character.
boolean
boolean isWhitespace(char c) Checks if the character is a whitespace character according to
isSpaceChar() method. boolean
Random Numbers
Method & Parameters Description Returns
int random() Generates a pseudo-random number between 0 and RAND_MAX. int
void randomSeed(unsigned long seed) Seeds the random number generator. Nothing
Bits and Bytes
Method & Parameters Description Returns
boolean bit(int value, int bitNumber) Gets the value of a specific bit. boolean
void bitClear(int &value, int bit) Clears a specific bit. Nothing
boolean bitRead(int value, int bitNumber) Reads the value of a specific bit. boolean
void bitSet(int &value, int bit) Sets a specific bit. Nothing
void bitWrite(int &value, int bit, int bitValue) Writes a value to a specific bit.
Nothing
byte highByte(int value) Returns the high byte of an int. byte
byte lowByte(int value) Returns the low byte of an int. byte
External Interrupts
Method & Parameters Description Returns
void attachInterrupt(int pin, void (*function)(void), int mode) Attaches an interrupt to a
specific pin. Nothing
void detachInterrupt(int pin) Detaches an interrupt from a specific pin. Nothing
Interrupts
Method & Parameters Description Returns
void interrupts() Enables interrupts globally. Nothing
void noInterrupts() Disables interrupts globally. Nothing
Stream
Method & Parameters Description Returns
int available() Returns the number of bytes available in the serial buffer. int
int read() Reads the next byte from the serial buffer. int
void flush() Waits for the transmission of outgoing serial data to complete. Nothing
int find(char *target) Searches for a target string in the serial buffer. int
int findUntil(char *target, char *terminate) Searches for a target string until a specified
termination string is found. int
int peek() Returns the next byte in the serial buffer without removing it. int
int readBytes(char *buffer, int length) Reads characters from the serial buffer into a
buffer. int
int readBytesUntil(char terminator, char *buffer, int length) Reads characters from the serial
buffer into a buffer until a terminator is found. int
String readString() Reads characters from the serial buffer into a String until a newline
character is found. String
String readStringUntil(char terminator) Reads characters from the serial buffer into a
String until a specified terminator is found. String
int parseInt() Reads characters from the serial buffer and converts them to an integer. int
float parseFloat() Reads characters from the serial buffer and converts them to a float.
float
void setTimeout(unsigned long timeout) Sets the maximum duration for find(),
findUntil(), parseInt(), and parseFloat(). Nothing
Serial
Method & Parameters Description Returns
if(Serial) Checks if the Serial object is available. boolean
int available() Returns the number of bytes available for reading. int
int availableForWrite() Returns the number of bytes available for writing. int
void begin(unsigned long baudrate) Initializes the Serial communication with the specified
baud rate. void
void end() Ends the Serial communication. void
int find(char *target) Searches for a target string in the serial buffer. int
int findUntil(char *target, char *terminate) Searches for a target string until a specified
termination string is found. int
void flush() Waits for the transmission of outgoing serial data to complete. void
float parseFloat() Reads characters from the serial buffer and converts them to a float.
float
int parseInt() Reads characters from the serial buffer and converts them to an integer. int
int peek() Returns the next byte in the serial buffer without removing it. int
size_t print() Prints data to the serial port. size_t
size_t println()Prints data to the serial port followed by a newline character. size_t
int read() Reads the next byte from the serial buffer. int
int readBytes(char *buffer, size_t length) Reads characters from the serial buffer into a
buffer. int
int readBytesUntil(char terminator, char *buffer, size_t length) Reads characters from the
serial buffer into a buffer until a terminator is found.int
String readString() Reads characters from the serial buffer into a String until a newline
character is found. String
String readStringUntil(char terminator) Reads characters from the serial buffer into a
String until a specified terminator is found. String
void setTimeout(unsigned long timeout) Sets the maximum duration for find(),
findUntil(), parseInt(), and parseFloat(). void
size_t write(uint8_t) Writes a byte to the serial port. size_t
void serialEvent() Called when data is available in the serial buffer. void
SPI
Method & Parameters Description Returns
SPISettings(uint32_t clock, uint8_t bitOrder, uint8_t dataMode) Creates an SPISettings
object with the specified clock, bit order, and data mode. SPISettings
void begin() Initializes the SPI library. void
void beginTransaction(SPISettings settings) Begins an SPI transaction with the specified
settings. void
void endTransaction() Ends the current SPI transaction. void
void end() Ends the SPI library. void
void setBitOrder(uint8_t bitOrder) Sets the bit order (MSBFIRST or LSBFIRST) for SPI
communication. void
void setClockDivider(uint8_t divider) Sets the clock divider for SPI communication.
void
void setDataMode(uint8_t dataMode) Sets the data mode for SPI communication.
void
byte transfer(byte value) Transfers a byte over SPI. byte
void usingInterrupt(int interruptNumber) Specifies which interrupt to use for SPI
transactions. void
I2C (Wire)
Method & Parameters Description Returns
void begin() Initializes the Wire library. void
void end() Ends the Wire library. void
int requestFrom(int address, int quantity) Requests data from a slave device with the
specified address and quantity of bytes. int
void beginTransmission(int address) Begins a transmission to the slave device with the
specified address. void
int endTransmission() Ends the transmission and returns the status. int
size_t write(uint8_t data) Writes a byte to the I2C bus. size_t
int available() Returns the number of bytes available for reading. int
int read() Reads a byte from the I2C bus. int
void setClock(uint32_t frequency) Sets the I2C clock frequency. void
void onReceive(void (*function)(int)) Sets a function to be called when data is
received by the slave. void
void onRequest(void (*function)(void)) Sets a function to be called when the master
requests data from the slave. void
void setWireTimeout(uint32_t timeout) Sets the timeout for I2C operations. void
void clearWireTimeoutFlag() Clears the timeout flag. void
bool getWireTimeoutFlag() Returns the timeout flag status. bool
Variables
Enums
Enum Type Enumeration Description
PinStatus HIGH / LOW Logical HIGH and LOW values (1 and 0).
PinMode INPUT / OUTPUT / INPUT_PULLUP / INPUT_PULLDOWN /
OUTPUT_OPENDRAIN Constants for specifying pin modes (0, 1, 2, 3, 4).
LED_BUILTIN Constant representing the built-in LED pin.*
true / false Boolean constants for true and false (1 and 0).
Conversion
Method & Parameter Description
(unsigned int) Type casting to unsigned int.
(unsigned long) Type casting to unsigned long.
byte() Type casting to byte.
char() Type casting to char.
float() Type casting to float.
int() Type casting to int.
long() Type casting to long.
word() Type casting to word.
Data Types
Method & Parameter Description
array Collection of variables of the same type.
bool Boolean data type.
boolean Boolean data type (synonym for bool).
byte 8-bit unsigned data type.
char 8-bit character data type.
double Double-precision floating-point data type.
float Single-precision floating-point data type.
int Integer data type.
long Long integer data type.
short Short integer data type.
size_t Unsigned integer data type.
string Sequence of characters (not a primitive type).
String() String class in Arduino.
unsigned char Unsigned 8-bit character data type.
unsigned int Unsigned integer data type.
unsigned long Unsigned long integer data type.
void Represents the absence of a type.
word 16-bit unsigned data type.
Variable Scope & Qualifiers
Method & Parameter Description
const Qualifier to define constants.
scope Not a specific keyword; refers to variable scope.
static Qualifier to declare static variables.
volatile Qualifier to declare volatile variables.
Utilities
Method & Parameter Description
PROGMEM Qualifier to store data in program memory.
sizeof() Operator to determine the size of a data type or variable.
Structure
Sketch
Method & Parameter Description
void loop() Main function for continuous code execution.
void setup() Initialization function, called once at startup.
Control Structure
Method & Parameter Description
break Exits a loop or switch statement.
continue Skips the rest of a loop iteration.
do...while Executes a block of code repeatedly while a specified condition is true.
else Part of the if-else statement.
for Creates a loop with a specified initialization, condition, and increment.
goto Transfers control to a labeled statement.
if Conditional statement for decision-making.
return Exits a function and optionally returns a value.
switch...case Multi-way branch statement.
while Creates a loop with a specified condition.
Further Syntax
Method & Parameter Description
#define (define) Macro definition for code substitution.
#include (include) Includes a file in the source code.
/* */ (block comment)Block comment for multiple lines.
// (single line comment) Single line comment.
; (semicolon) Statement terminator.
{} (curly braces) Block of code, often used with control structures.
Arithmetic Operators
Method & Parameter Description
% (remainder) Modulo operator for finding the remainder of a division.
* (multiplication) Multiplication operator.
+ (addition) Addition operator.
- (subtraction) Subtraction operator.
/ (division) Division operator.
= (assignment operator) Assignment operator.
Comparison Operators
Method & Parameter Description
!= (not equal to) Checks if two values are not equal.
< (less than) Checks if the left value is less than the right value.
<= (less than or equal to) Checks if the left value is less than or equal to the right value.
== (equal to) Checks if two values are equal.
> (greater than) Checks if the left value is greater than the right value.
>= (greater than or equal to) Checks if the left value is greater than or equal to the right
value.
Boolean Operators
Method & Parameter Description
! (logical not) Inverts the logical value, true becomes false and vice versa.
&& (logical and) Logical AND operator, returns true if both operands are true.
(logical or) Logical OR operator, returns true if at least one operand is true.
Pointer Access Operators
Method & Parameter Description
& (reference operator)Returns the memory address of a variable.
* (dereference operator) Accesses the value pointed to by a pointer.
Bitwise Operators
Method & Parameter Description
& (bitwise and) Performs bitwise AND operation.
<< (bitshift left) Shifts bits to the left.
>> (bitshift right) Shifts bits to the right.
^ (bitwise xor) Performs bitwise XOR (exclusive OR) operation.
(bitwise or) Performs bitwise OR operation.
~ (bitwise not) Inverts all bits.
Compound Operators
Method & Parameter Description
%= (compound remainder) Performs a modulo operation and assigns the result to the left
operand.
&= (compound bitwise and) Performs a bitwise AND operation and assigns the result to the
left operand.
*= (compound multiplication) Multiplies the left operand by the right operand and
assigns the result to the left operand.
++ (increment) Increments the value of the operand by 1.
+= (compound addition) Adds the right operand to the left operand and assigns the result
to the left operand.
-- (decrement) Decrements the value of the operand by 1.
-= (compound subtraction) Subtracts the right operand from the left operand and assigns
the result to the left operand.
/= (compound division) Divides the left operand by the right operand and assigns the
result to the left operand.
^= (compound bitwise xor) Performs a bitwise XOR operation and assigns the result to the
left operand.
= (compound bitwise or) Performs a bitwise OR operation and assigns the result to the
left operand.
Last revision08/02/2024
A variable is a place to store a piece of data. It has a name, a value, and a type. For example,
this statement (called a declaration):
creates a variable whose name is pin, whose value is 13, and whose type is int. Later on in the
program, you can refer to this variable by its name, at which point its value will be looked up
and used. For example, in this statement:
pinMode(pin, OUTPUT);
it is the value of pin (13) that will be passed to the pinMode() function. In this case, you don't
actually need to use a variable, this statement would work just as well:
pinMode(13, OUTPUT);
The advantage of a variable in this case is that you only need to specify the actual number of
the pin once, but you can use it lots of times. So if you later decide to change from pin 13 to
pin 12, you only need to change one spot in the code. Also, you can use a descriptive name to
make the significance of the variable clear (e.g. a program controlling an RGB LED might
have variables called redPin, greenPin, and bluePin).
A variable has other advantages over a value like a number. Most importantly, you can
change the value of a variable using an assignment (indicated by an equals sign). For
example:
pin = 12;
will change the value of the variable to 12. Notice that we don't specify the type of the
variable: it's not changed by the assignment. That is, the name of the variable is permanently
associated with a type; only its value changes. [1] Note that you have to declare a variable
before you can assign a value to it. If you include the preceding statement in a program
without the first statement above, you'll get a message like: "error: pin was not declared in
this scope".
When you assign one variable to another, you're making a copy of its value and storing that
copy in the location in memory associated with the other variable. Changing one has no effect
on the other. For example, after:
COPY
int pin = 13;
int pin2 = pin;
pin = 12;
only pin has the value 12; pin2 is still 13.
Now what, you might be wondering, did the word "scope" in that error message above mean?
It refers to the part of your program in which the variable can be used. This is determined by
where you declare it. For example, if you want to be able to use a variable anywhere in your
program, you can declare at the top of your code. This is called a global variable; here's an
example:
COPY
Explain
int pin = 13;
void setup()
{
pinMode(pin, OUTPUT);
}
void loop()
{
digitalWrite(pin, HIGH);
}
As you can see, pin is used in both the setup() and loop() functions. Both functions are
referring to the same variable, so that changing it one will affect the value it has in the other,
as in:
COPY
Explain
int pin = 13;
void setup()
{
pin = 12;
pinMode(pin, OUTPUT);
}
void loop()
{
digitalWrite(pin, HIGH);
}
Here, the digitalWrite() function called from loop() will be passed a value of 12, since that's
the value that was assigned to the variable in the setup() function.
If you only need to use a variable in a single function, you can declare it there, in which case
its scope will be limited to that function. For example:
COPY
Explain
void setup()
{
int pin = 13;
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
}
In this case, the variable pin can only be used inside the setup() function. If you try to do
something like this:
COPY
void loop()
{
digitalWrite(pin, LOW); // wrong: pin is not in scope here.
}
you'll get the same message as before: "error: 'pin' was not declared in this scope". That is,
even though you've declared pin somewhere in your program, you're trying to use it
somewhere outside its scope.
Why, you might be wondering, wouldn't you make all your variables global? After all, if I
don't know where I might need a variable, why should I limit its scope to just one function?
The answer is that it can make it easier to figure out what happens to it. If a variable is global,
its value could be changed anywhere in the code, meaning that you need to understand the
whole program to know what will happen to the variable. For example, if your variable has a
value you didn't expect, it can be much easier to figure out where the value came from if the
variable has a limited scope.
[1] In some languages, like Python®, types are associated with values, not variable names,
and you can assign values of any type to a variable. This is referred to as dynamic typing.
Last revision08/02/2024
Segmenting code into functions allows a programmer to create modular pieces of code that
perform a defined task and then return to the area of code from which the function was
"called". The typical case for creating a function is when one needs to perform the same
action multiple times in a program.
For programmers accustomed to using BASIC, functions in Arduino provide (and extend) the
utility of using subroutines (GOSUB in BASIC).
Functions help the programmer stay organized. Often this helps to conceptualize the program.
Functions codify one action in one place so that the function only has to be thought out and
debugged once.
This also reduces chances for errors in modification, if the code needs to be changed.
Functions make the whole sketch smaller and more compact because sections of code are
reused many times. They make it easier to reuse code in other programs by making it more
modular, and as a nice side effect, using functions also often makes the code more readable.
There are two required functions in an Arduino sketch, setup() and loop(). Other functions
must be created outside the brackets of those two functions. As an example, we will create a
simple function to multiply two numbers.
Example
FuncAnatomy
To "call" our simple multiply function, we pass it parameters of the datatype that it is
expecting:
COPY
Explain
void loop(){
int i = 2;
int j = 3;
int k;
COPY
Explain
void setup(){
Serial.begin(9600);
}
void loop() {
int i = 2;
int j = 3;
int k;
COPY
int ReadSens_and_Condition(){
int i;
int sval = 0;
for (i = 0; i < 5; i++){
sval = sval + analogRead(0); // sensor on analog pin 0
}
COPY
int sens;
sens = ReadSens_and_Condition();
As you can see, even if a function does not have parameters and no returns is expected "("
and ")" brackets plus ";" must be given.
Arduino Sketches
Get to know how sketches work, and how they are uploaded to an Arduino.
Last revision08/02/2024
In the getting started guide (Windows, MacOS, Linux), you uploaded a sketch that blinks an
LED. In this tutorial, you'll learn how each part of that sketch works.
A sketch is the name that Arduino uses for a program. It's the unit of code that is uploaded to
and run on an Arduino board.
Comments
The first few lines of the Blink sketch are a comment:
COPY
Explain
/*
* Blink
* then off for one second, and so on... We use pin 13 because,
* http://www.arduino.cc/en/Tutorial/Blink
*/
Everything between the /* and */ is ignored by the Arduino when it runs the sketch (the * at
the start of each line is only there to make the comment look pretty, and isn't required). It's
there for people reading the code: to explain what the program does, how it works, or why it's
written the way it is. It's a good practice to comment your sketches, and to keep the
comments up-to-date when you modify the code. This helps other people to learn from or
modify your code.
There's another style for short, single-line comments. These start with // and continue to the
end of the line. For example, in the line:
COPY
int ledPin = 13; // LED connected to digital pin 13
the message "LED connected to digital pin 13" is a comment.
Variables
A variable is a place for storing a piece of data. It has a name, a type, and a value. For
example, the line from the Blink sketch above declares a variable with the name ledPin, the
type int, and an initial value of 13. It's being used to indicate which Arduino pin the LED is
connected to. Every time the name ledPin appears in the code, its value will be retrieved. In
this case, the person writing the program could have chosen not to bother creating the ledPin
variable and instead have simply written 13 everywhere they needed to specify a pin number.
The advantage of using a variable is that it's easier to move the LED to a different pin: you
only need to edit the one line that assigns the initial value to the variable.
Often, however, the value of a variable will change while the sketch runs. For example, you
could store the value read from an input into a variable. There's more information in the
Variables tutorial.
Functions
A function (otherwise known as a procedure or sub-routine) is a named piece of code that can
be used from elsewhere in a sketch. For example, here's the definition of the setup() function
from the Blink example:
COPY
Explain
void setup()
{
You can call a function that's already been defined (either in your sketch or as part of the
Arduino language). For example, the line pinMode(ledPin, OUTPUT); calls the pinMode()
function, passing it two parameters: ledPin and OUTPUT. These parameters are used by the
pinMode() function to decide which pin and mode to set.
COPY
digitalWrite(ledPin, HIGH);
set the ledPin (pin 13) to HIGH, or 5 volts. Writing a LOW to pin connects it to ground, or 0
volts.
The delay() causes the Arduino to wait for the specified number of milliseconds before
continuing on to the next line. There are 1000 milliseconds in a second, so the line:
COPY
delay(1000);
creates a delay of one second.
Exercises
1. Change the code so that the LED is on for 100 milliseconds and off for 1000.
2. Change the code so that the LED turns on when the sketch starts and stays on.
See Also
setup()
loop()
pinMode()
digitalWrite()
delay()
Last revision08/02/2024
Hardware Required
MKR Vidor 4000
Field Programmable Gate Arrays
Field Programmable Gate Arrays, in short FPGAs are a relatively old way of creating custom
hardware eliminating the costs associated with silicon foundries. Unfortunately most of the
complexity of chip design are still there and this is the reason why most people prefers to use
off the shelf chips, often accepting their limitations, rather than take the challenge to have an
optimized, efficient design with exactly the hardware they need.
As it happens with Software, where there are lots of libraries you can start from, also for
FPGAs there are "libraries" called IP blocks, however these are usually quite expensive and
lack a standardized "plug and play" interface which causes headaches when integrating
everything in a system. What Arduino is trying to do introducing FPGAs in its product line is
to take advantage of the flexibility of programmable hardware specifically to provide an
extendable set of peripherals for microcontrollers taking away most of the complexity. Of
course to achieve this it's necessary to impose some limitations and define a standard way to
interconnect blocks so that it can be done automatically.
The first step is defining a set of standard interfaces that must be strictly responding to a
given set of rules but before diving into this it's important to define which kind of interfaces
we may need. Since we are interfacing with a microcontroller the first port we need to define
is a bus to interconnect processor with peripherals. Such bus should at least exist in the
controller and peripheral flavors where signals are the same but with inverted directions. For
some additional details on buses and controller/peripheral architecture please check this
document.
A second interface, which is important but can't be standardized is the input/output signals
that connect to the external world. Here we can't define a standard as each block will provide
its own set of signals however we can just bundle a set of signals in a group which we'll call a
conduit.
Finally there is a third class of interfaces which may become useful which carries streaming
data. In this case we want to transfer a continuous stream of data but also want to be able to
pause the flow if the receiving block is not able to process it, hence along with data we also
need some kind of flow control signals pretty much like it happens on a UART.
Since we want to standardize a bit also on readability we also want to set some coding
conventions. Here of course there are many different religions which rule on spaces/tabs,
notation and so on so we're picking one we like...
Talking about religion we end up talking also about languages... we prefer (System)Verilog
over VHDL and most of our IP blocks are coded with it. The reason of our choice is that
Verilog in general is more similar to C and also allows very nice constructs that facilitate
creating parametric blocks.
Coding Conventions
We use a prefix in front of every declared entity so that it identifies its type, variable name is
completely upper case and multiple words are separated by underscores. In particular:
Prefix Description
w Wire, for all combinatorial signals, for example wDATA. Typically defined with wire
directive
r Reg, for all sequential signals, for example rSHIFTER. Typically defined with reg
directive
i Input, for all input signals in module declaration, for example iCLK. Typically
defined with input directive
o Output, for all output signals in module declaration, for example oREAD. Typically
defined with output directive
b Bidirectional, for all inout signals in module declaration, for example bSDA.
Typically defined with inout directive
p Parameter, for all parameters that can be used to parametrize block, for example
pCHANNELS. Typically defined with param directive
c Constant, for all definitions which are constant or are derived values and can't be
directly used to parametrize the block. For example cCHANNEL_BITS. Typically defined
with localparam directive
e Enumerated, for all the possible constant values used by one or more signals or
registers. For example a state machine state can be defined as eSTATE. Typically defined
with enum directive
We prefer spaces over tabs! The reason is that regardless of the tab size code always looks
good.
Interface prototypes
Lightweight Bus
A bus to interconnect peripherals. By convention data bus is 32 bits while address bus is
variable width, based on the number of registers being exposed. A bus requires the following
set of signals:
Pipelined Bus
A bus to interconnect complex blocks that can handle more than one command at time and
responds in variable time to requests. This bus extends the Lightweight bus by adding the
following signals:
This behavior is also referred as 1 clock read latency and basically means that while
peripheral can still have a variable number of clocks to respond to a READ or WRITE
operation using the optional WAIT_REQUEST signal, this would lock the controller
preventing it to perform other operations. In a way this can be considered similar to using
busy loops in programming versus delays which yield to OS in order to do multitasking.
The controller will assert WAIT_REQUEST until it is ready to accept an operation. In case of
writes, BURST_COUNT and ADDRESS are sampled only on the first strobe, after which
peripheral will expect the WRITE strobe to be asserted for the number of words requested
and will automatically increment address. For read operations a single READ strobe,
performed when WAIT_REQUEST is not asserted, will tell the peripheral to read
BURST_COUNT words that will be returned by asserting READ_DATAVALID for the
requested number of words. After a read operation has been initiated it's up to the peripheral
to accept or not more operations but in general it should be possible to have at least two
concurrent operations to benefit from the Pipelined Bus.
Streaming interface
Coming soon
COPY
Explain
module COUNTER #(
pWIDTH=8
)(
input iCLK,
input iRESET,
output reg [pWIDTH-1:0] oCOUNTER
);
endmodule
Here we just defined the prototype of the module and defined its input/output ports, now we
have to add some useful logic to it by adding some code between module header and the
endmodule statement.
Since we started with a counter example let's continue with that and write some code that
actually implements it:
COPY
Explain
module COUNTER #(
pWIDTH=8
)(
input iCLK,
input iRESET,
output [pWIDTH-1:0] oCOUNTER
);
always @(posedge iCLK)
begin
if (iRESET) begin
oCOUNTER<=0;
end else begin
oCOUNTER<= oCOUNTER+1;
end
end
endmodule
The code above is pretty self explanatory... at every positive clock edge, if we see input
iRESET high we reset the counter, otherwise we increment it by one... note that having a
reset signal restoring our block to a known state is often useful but not always necessary.
Now... this is interesting however we did something a bit tricky... we declared oCOUNTER
as output reg, which means we are saying this is not just a bunch of wires but it has memory.
This way we can use the <= assignment which is "registered" which means that the
assignment will be kept for as long as the next clock cycle.
Another way we could do this is removing the reg statement in the module declaration and
define the counter as follows:
COPY
Explain
module COUNTER #(
pWIDTH=8
)(
input iCLK,
input iRESET,
output [pWIDTH-1:0] oCOUNTER
);
reg [pWIDTH-1:0] rCOUNTER;
always @(posedge iCLK)
begin
if (iRESET) begin
rCOUNTER<=0;
end else begin
rCOUNTER<= rCOUNTER+1;
end
end
assign oCOUNTER=rCOUNTER;
endmodule
This is basically the same stuff but we defined a register, worked on it and then assigned with
the "continuous" = assignment it to the output signal. The differsence here is that while <=
means the signal changes only at clock edges = assigns the value continuously so the signal
will eventually change at any time, however if we assign it as we are doing in the example to
a register that changes only on clock edges the resulting signal is basically just an alias.
Interestingly assignments, like any other statement in hardware description languages are
parallel, which means that their order in the code is not so much relevant as they are all
executed in parallel so we could have assigned oCOUNTER to rCOUNTER also before the
always block. We'll come back to this as it's not completely true that order doesn't matter...
Another interesting use of continuous assignments is the possibility to create logic equations.
For example we could rewrite the counter the following way:
COPY
Explain
module COUNTER #(
pWIDTH=8
)(
input iCLK,
input iRESET,
output [pWIDTH-1:0] oCOUNTER
);
reg [pWIDTH-1:0] rCOUNTER;
wire [pWIDTH-1:0] wNEXT_COUNTER;
assign wNEXT_COUNTER = rCOUNTER+1;
assign oCOUNTER = rCOUNTER;
always @(posedge iCLK)
begin
if (iRESET) begin
rCOUNTER<=0;
end else begin
rCOUNTER<= wNEXT_COUNTER;
end
end
endmodule
We're basically still doing the same stuff but we have done it in a way that makes it a bit
more clear logically. Basically we are assigning continuously the signal wNEXT_COUNTER
to the value of the rCOUNTER plus one. This means that wNEXT_COUNTER will (almost)
immediately change as soon as rCOUNTER changes value however rCOUNTER will be
updated only on the next positive clock edge (as it has a <= assignment) so the result is still
that rCOUNTER changes only on clock edge.
COPY
Explain
reg [pWIDTH-1:0] rCOUNT_UP, rCOUNT_DOWN;
always @(posedge iCLK)
begin
if (iRESET) begin
rCOUNT_UP<=0;
rCOUNT_DOWN<=0;
end else begin
rCOUNT_UP<= rCOUNT_UP+1;
rCOUNT_DOWN<= rCOUNT_DOWN-1;
end
end
Of course if everything gets executed in parallel we need to have a way to sequentialize
statements which can be done by creating a simple state machine. A state machine is a system
which generates outputs based on inputs AND its internal state. In a sense our counter was
already a state machine as we have an output (oCOUNTER) that changes based on the
previous state of the machine (rCOUNTER), however let's do something more interesting and
create a state machine that creates a pulse of a given length when we start it. The machine
will have three states: eST_IDLE, eST_PULSE_HIGH and eST_PULSE_LOW. In the
eST_IDLE we will sample the input command and when that is received we transition to
eST_PULSE_HIGH, where we will stay for the given number of clocks, which we'll
parametrize with pHIGH_COUNT, then we will transition to eST_PULSE_LOW where we'll
stay for pLOW_COUNT and then get back to eST_IDLE... Let's have a look at how this turns
out in code:
COPY
Explain
module PULSE_GEN #(
pWIDTH=8,
pHIGH_COUNT=240,
pLOW_COUNT=40
)(
input iCLK,
input iRESET,
input iPULSE_REQ,
output reg oPULSE
);
reg [pWIDTH-1:0] rCOUNTER;
enum reg [1:0] {
eST_IDLE,
eST_PULSE_HIGH,
eST_PULSE_LOW
} rSTATE;
always @(posedge iCLK)
begin
if (iRESET) begin
rSTATE<=eST_IDLE;
end else begin
case (rSTATE)
eST_IDLE: begin
if (iPULSE_REQ) begin
rSTATE<= eST_PULSE_HIGH;
oPULSE<= 1;
rCOUNTER <= pHIGH_COUNT-1;
end
end
eST_PULSE_HIGH: begin
rCOUNTER<= rCOUNTER-1;
if (rCOUNTER==0) begin
rSTATE<= eST_PULSE_LOW;
oPULSE<= 0;
rCOUNTER<= pLOW_COUNT-1;
end
end
eST_PULSE_LOW: begin
rCOUNTER<= rCOUNTER-1;
if (rCOUNTER==0) begin
rSTATE<= eST_IDLE;
end
end
endcase
end
end
endmodule
Here we see a number of new things we need to talk about. First of all we are defining the
rSTATE variable using enum. This helps in assigning the state to easily understandable
values rather than hard-coded numbers and has the advantage that you can insert states easily
without the need to rewrite all your state machine.
Secondly we are introducing the case/endcase block which allows us to define different
behaviours depending on the state of a signal. The syntax is very similar to C so it should be
familiar to most readers. It's important to note that the statements within the various case
blocks will still execute in parallel but since they are conditioned by different values of the
variable being examined only one at time will be enabled. Looking at the eST_IDLE case we
see that we stay in the state forever until we sense iPULSE_REQ goes high, in which case we
change state, reset the counter to the period of the high state and start outputting the pulse.
Note that since oPULSE is registered it will retain its state until it's assigned again. In the
next state things get a bit more complicated... at each clock we decrement the counter, then if
counter reaches 0 we also change state, change oPULSE to 0 AND we assign rCOUNTER
again. Since the two assignments are executed in parallel we need to know what this means
and lucky enough all HDL mandate that if two parallel statements are executed on the same
register only the last one will be really executed so the meaning of what we just wrote is that
normally we decrement the counter but when counter reaches 0 we change state and re-
initialize it to pLOW_COUNT.
At this point what happens in eST_PULSE_LOW becomes pretty clear as we just decrement
the counter and get back to eST_IDLE as soon as it reaches 0. Note that when we get back to
eST_IDLE rCOUNTER is decremented again so the result is that rCOUNTER will be 0xff
(or -1) in eST_IDLE but we don't really care as we will reset it to the proper value when we
receive iPULSE_REQ.
Although we could have reset rCOUNTER also when exiting eST_PULSE_LOW, in HDL
it's always better to do only what's really necessary as anything more will consume resources
and make our hardware slower. At the beginning this may seem dangerous but with some
experience it will become easy to see how this can help. This same concept applies to reset
logic. Unless it is really necessary, depending on how it is implemented it can consume
resources and worsen system speed so it should be used with care.
In order to do so we need a counter and several comparators that tells us when the counter is
above given values so that we can toggle the outputs. Since we also want to have the PWM
frequency to be programmable we need to have the counter running at a frequency different
than the base one we use for our system so that its period is exactly what we need. In order to
do so we use a prescaler which basically is another counter that divides the base clock down
to a lower value in a way similar to baud rate generators used in UARTs.
COPY
Explain
module PWM #(
parameter pCHANNELS=16,
parameter pPRESCALER_BITS=32,
parameter pMATCH_BITS=32
)
(
input iCLK,
input iRESET,
input [$clog2(2*pCHANNELS+2)-1:0] iADDRESS,
input [31:0] iWRITE_DATA,
input iWRITE,
output reg [pCHANNELS-1:0] oPWM
);
// register declaration
reg [pPRESCALER_BITS-1:0] rPRESCALER_CNT;
reg [pPRESCALER_BITS-1:0] rPRESCALER_MAX;
reg [pMATCH_BITS-1:0] rPERIOD_CNT;
reg [pMATCH_BITS-1:0] rPERIOD_MAX;
reg [pMATCH_BITS-1:0] rMATCH_H [pCHANNELS-1:0];
reg [pMATCH_BITS-1:0] rMATCH_L [pCHANNELS-1:0];
reg rTICK;
integer i;
always @(posedge iCLK)
begin
// logic to interface with bus.
// register map is as follows:
// 0: prescaler value
// 1: PWM period
// even registers >=2: value at which PWM output is set high
// odd registers >=2: value at which PWM output is set low
if (iWRITE) begin
// the following statement is executed only if address is >=2. case on iADDRESS[0]
// selects if address is odd (iADDRESS[0]=1) or even (iADDRESS[0]=0)
if (iADDRESS>=2) case (iADDRESS[0])
0: rMATCH_H[iADDRESS[CLogB2(pCHANNELS):1]-1]<= iWRITE_DATA;
1: rMATCH_L[iADDRESS[CLogB2(pCHANNELS):1]-1]<= iWRITE_DATA;
endcase
else begin
// we get here if iADDRESS<2
case (iADDRESS[0])
0: rPRESCALER_MAX<=iWRITE_DATA;
1: rPERIOD_MAX<=iWRITE_DATA;
endcase
end
end
// prescaler is always incrementing
rPRESCALER_CNT<=rPRESCALER_CNT+1;
rTICK<=0;
if (rPRESCALER_CNT>= rPRESCALER_MAX) begin
// if prescaler is equal or greater than the max value
// we reset it and set the tick flag which will trigger the rest of the logic
// note that tick lasts only one clock cycle as it is reset by the rTICK<= 0 above
rPRESCALER_CNT<=0;
rTICK <=1;
end
if (rTICK) begin
// we get here each time rPRESCALER_CNT is reset. from here we increment the PWM
// counter which is then clocked at a lower frequency.
rPERIOD_CNT<=rPERIOD_CNT+1;
if (rPERIOD_CNT>=rPERIOD_MAX) begin
// and of course we reset the counter when we reach the max period.
rPERIOD_CNT<=0;
end
end
// this block implements the parallel comparators that actually generate the PWM outputs
// the for loop actually generates an array of logic that compares the counter with
// the high and low match values for each channel and set the output accordingly.
for (i=0;i<pCHANNELS;i=i+1) begin
if (rMATCH_H[i]==rPERIOD_CNT)
oPWM[i] <=1;
if (rMATCH_L[i]==rPERIOD_CNT)
oPWM[i] <=0;
end
end
endmodule
There are a number of new things here to learn so let's start from the module declaration.
Here we are using a built in function to establish the required bit width of the address bus.
The purpose is to limit the address span to the minimum required for the registers so for
example if we want 10 channels we need a total of 22 addresses. Since each address bit
doubles the number of addresses we can use we need a total of 5 bits which result in 32 total
addresses. In order to make this parametric we define iADDRESS width as
$clog2(2*pCHANNELS+2) and we define registers as a 2 dimensional array.
Actually there are two ways to make a multidimensional array and here we are using the
"unpacked" one, which basically defines the registers as separate entities by adding indices
on the left side of the register declaration. The other way, we are not using in this example is
the "packed" one in which indices are both on the left side of the declaration and the result is
that the 2D array can also be seen as a single big register containing the concatenation of all
the registers.
Another interesting trick here is how we define the logic that handles registers. First of all we
are just implementing write only registers so you won't find the iREAD and iREAD_DATA
signals Secondly we wanted to have a parametric register set where only the first two
registers are always present whereas the rest are dynamically defined and handled based on
the number of channels we want to implement. In order to do so we note that in a binary
number the least significant bit defines whether the number is odd or even. Since we have
two registers per channel this comes handy as we can differentiate our behaviour depending
on whether we are below address 2 or not.
If we are below address 2 we implement the common registers which are the prescaler count
and the counter period. If we are above 2 we use the LSB to decide if we are writing the
value for the high or low comparator.
Another interesting thing we're doing here is that we are using continuous assignment to
determine a strobe and a direction from the quadrature signals out of the encoder. In the code
there are simple graphs showing how the waveforms look like, although in order to fully
understand how the waveforms come out you have to consider that the equations use signals
at different points in time. This is done by simply using the shift register used to synchronize
asynchronous inputs also to delay them so tapping into a different point of the shift register
we can see how signal was the clock before. In particular, if we move towards the input of the
shift register we get "newer" data whereas if we move towards the end we get "older" data. If
we look at the equations we see we are using the ^ operator which is a logical exclusive or
(XOR) which returns 1 if the two operands are different and 0 otherwise.
Looking at the waveforms we see that the strobe generates a pulse whenever A or B have an
edge and this is done by simply xoring each signal with its delayed version. The direction
signal instead is a bit more complex but we notice that it is constantly either 0 or 1 when the
strobe signal is high depending on the direction the encoder is rotating. Actually we see there
are pulses on the direction signal but these are not coincident with strobes so those will be
ignored.
One thing that may not look obvious at first look is that the equations are parallelly
calculating the same logic for all the inputs, in fact the rRESYNC_ENCODER registers are
packed bidimensional arrays arranged so that the first index identifies the tap of the shift
register and the second index is the encoder channel. This means that whenever we reference
rRESYNC_ENCODER with a specific index we are selecting a mono-dimensional array
containing all the encoder inputs at once delayed by the amount of clocks specified by the
index. This also means that when we perform a bitwise logic operation on an array we are
actually instantiating multiple parallel logic equations at the same time. Note that this can be
done only because the array is "packed" as with "unpacked" arrays elements are considered
separate and can't take part in equations this way and they have to be addressed singularly.
As we did for the other examples the block implements multiple inputs and does this by using
a for loop that checks for the enable signal (which again is an array as wide as the number of
channels) and when that is high it checks for the direction and based on that either increments
or decrements the counter for that channel. This is easily done using the ? : operator
(conditional expression) which works exactly like in C.
Finally the bus interface is pretty simple because the only registers we have are the read only
counters and we can implement this simply by checking the read signal and assigning output
data with the array of counters indexed by the address, pretty much like it was a RAM.
COPY
Explain
module QUAD_ENCODER #(
pENCODERS=2,
pENCODER_PRECISION=32
)(
input iCLK,
input iRESET,
// AVALON PERIPHERAL INTERFACE
input [$clog2(pENCODERS)-1:0] iAVL_ADDRESS,
input iAVL_READ,
output reg [31:0] oAVL_READ_DATA,
// ENCODER INPUTS
input [pENCODERS-1:0] iENCODER_A,
input [pENCODERS-1:0] iENCODER_B
);
// bidimensional arrays containing encoder input states at 4 different points in time
// the first two delay taps are used to synchronize inputs with the internal clocks
// while the other two are used to compare two points in time of those signals.
reg [3:0][pENCODERS-1:0] rRESYNC_ENCODER_A,rRESYNC_ENCODER_B;
// bidimensional arrays containing the counters for each channel
reg [pENCODERS-1:0][pENCODER_PRECISION-1:0] rSTEPS;
// encoder decrementing
// A __----____----__
// B ____----____----
// ENABLE __-_-_-_-_-_-_-_
// DIR __---_---_---_--
//
// encoder incrementing
// A ____----____----
// B __----____----__
// ENABLE __-_-_-_-_-_-_-_
// DIR ___-___-___-___-
wire [pENCODERS-1:0] wENABLE =
rRESYNC_ENCODER_A[2]^rRESYNC_ENCODER_A[3]^rRESYNC_ENCODER_B[2]^r
RESYNC_ENCODER_B[3];
wire [pENCODERS-1:0] wDIRECTION =
rRESYNC_ENCODER_A[2]^rRESYNC_ENCODER_B[3];
integer i;
initial rSTEPS <=0;
always @(posedge iCLK)
begin
if (iRESET) begin
rSTEPS<=0;
rRESYNC_ENCODER_A<=0;
rRESYNC_ENCODER_B<=0;
end
else begin
// implement shift registers for each channel. since arrays are packed we can treat that as a
monodimensional array
// and by adding inputs at the bottom we are effectively shifting data by one bit
rRESYNC_ENCODER_A<={rRESYNC_ENCODER_A,iENCODER_A};
rRESYNC_ENCODER_B<={rRESYNC_ENCODER_B,iENCODER_B};
for (i=0;i<pENCODERS;i=i+1)
begin
// if strobe is high..
if (wENABLE[i])
// increment or decrement based on direction
rSTEPS[i] <= rSTEPS[i]+ ((wDIRECTION[i]) ? 1 : -1);
end
// if PERIPHERAL interface is being read...
if (iAVL_READ)
begin
// return the value of the counter indexed by the address
oAVL_READ_DATA<= rSTEPS[iAVL_ADDRESS];
end
end
end
endmodule
This is probably a great example of how elegant and concise can be hardware description for
such design as we described an highly parametric design where we can change counter depth
and number of channels and the code scales up accordingly generating all the related logic in
a very readable way. Of course there are different ways of doing the same thing and this one
of the most concise that at the same time requires a bit more understanding of the capabilities
of (system)Verilog.
One essential part of a microcontroller is its memory; memory stores information temporarily
or permanently in microcontrollers, and can be used for several purposes. In this article, we
will explore memory organization in microcontrollers, focusing on those present in Arduino®
boards. We will also explore several ways to manage, measure, and optimize memory usage
in Arduino-based systems.
What is Memory?
Memory blocks are essential parts of modern embedded systems, especially microcontroller-
based ones. Memory blocks are semiconductor devices that store and retrieve information or
data; a microcontroller central processing unit (CPU) uses and processes data stored in
memory blocks to perform specific tasks.
As shown in the image below, memory blocks in microcontrollers are usually described as
arrays. Memory arrays are divided into cells that can store data and be accessed using a
unique identifier representing its address or position relative to the memory array.
Information in memory cells is stored using binary digits (bits), usually organized in bytes (8-
bits); it can also be retrieved later by the MCU or other components of a microcontroller-
based system.
In the early days of computing, two computer architectures, i.e., the organization of the
components inside a computing system, emerged: von Neumann and Harvard.
Both are accessed by the CPU using the same communications bus, as shown below. Von
Neumann's architecture is fundamental since nearly all digital computers design have been
based on this architecture.
Harvard Architecture
The Harvard architecture, named after the Harvard Mark I relay-based computer, was first
introduced in the mid '40s. This architecture's main characteristic is that it uses two separate
memory units, one for storing program instructions and one for storing program data. Both
memory units in the Harvard architecture are accessed by the CPU using different
communication buses.
Harvard architecture.
Harvard architecture.
Microcontrollers are usually used in embedded applications. They must perform defined tasks
reliably and efficiently, with low or constrained resources; this is why the Harvard
architecture model is mainly used in microcontrollers: microcontrollers have a small program
and data memory that needs to be accessed simultaneously. However, Harvard architecture is
not always used in microcontrollers; some microcontroller families use hybrid or Von
Neumann architecture models.
RAM and ROM in microcontroller-based systems are organized into three main categories:
Flash
RAM
EEPROM
Flash
Flash memory in microcontroller-based systems is part of its ROM. The flash memory is
where the system's firmware is stored to be executed. For example, think of the famous
Blink.ino sketch: when we compile this sketch, we create a binary file that is later stored in
the flash memory of an Arduino board. The sketch is then executed when the board is
powered on.
RAM
RAM in microcontroller-based systems is where the system's temporary data or run-time data
is stored; for example, the variables created by functions of a program. RAM in
microcontrollers usually is SRAM; this is a type of RAM that uses a flip-flop to store one bit
of data. There is also another type of RAM that can be found in microcontrollers: DRAM.
EEPROM
In microcontroller-based systems, Erasable Programmable Read-Only Memory, or
EEPROM, is also part of its ROM; actually, Flash memory is a type of EEPROM. The main
difference between Flash memory and EEPROM is how they are managed; EEPROM can be
managed at the byte level (write or erased) while Flash can be managed at the block level.
Important to mention about AVR-based Arduino boards is how their SRAM is organized into
different sections:
Text
Data
BSS
Stack
Heap
The text section contains instructions loaded into the flash memory; data section contains
variables initialized in the sketch, BSS section contains uninitialized data, stack section stores
data of functions and interrupts, and heap section stores variables created during run time.
The ARM-based microcontroller's memory is organized into the following sections within the
address type mentioned previously:
Virtual address:
ROM
RAM
Flash
Peripherals
The following table summarizes a specific Arduino® board's memory allocation:
For example, the IDE's compiler output console an AVR-based Arduino® board, the Nano, is
shown in the image below:
The IDE's compiler output console log for an ARM-based Arduino® board, the MKR WAN
1310, is shown in the image below:
The IDE's compiler output console log for another ARM-based Arduino®, the Portenta H7, is
shown in the image below:
Notice that the compiler's output changes depending on if the board is AVR-based or ARM-
based.
COPY
Explain
void display_freeram() {
Serial.print(F("- SRAM left: "));
Serial.println(freeRam());
}
int freeRam() {
extern int __heap_start,*__brkval;
int v;
return (int)&v - (__brkval == 0
? (int)&__heap_start : (int) __brkval);
}
Remember that the heap section is where variables created during the run time are stored. In
the code, __heap_start and __brkval are as following:
COPY
Explain
extern "C" char* sbrk(int incr);
void display_freeram(){
Serial.print(F("- SRAM left: "));
Serial.println(freeRam());
}
int freeRam() {
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
}
The code above is taken from Michael P. Flaga's library Arduino-MemoryFree.
COPY
Explain
#include <EEPROM.h>
void setup() {
}
void loop {
// Write data into an specific address of the EEPROM memory
EEPROM.write(address, value);
COPY
Explain
#include <EEPROM.h>
void setup() {
}
void loop {
for (int i = 0 ; i < EEPROM.length() ; i++) {
// Clear EEPROM memory
EEPROM.write(i, 0);
}
For more information on how to manage the EEPROM memory, you can refer to this guide.
The memory usage optimization process also implies reduced computational complexities,
trimming down extra time required to process tasks while using fewer memory resources to
do the same tasks. The memory usage optimization process may help the overall code
optimization process, as it will handle how the memory is managed more suitably by
requiring intelligent algorithms development.
This leads to a compact code structure, which is much easier to understand when debugging
is required and demands the developer consider computing complexity when designing the
code structure or such a specific algorithm.
The ideal way to use the Print Line command is to use the F() String Wrapper around the
literals. See the example below:
String Wrapper
Serial.print() or Serial.println() instructions uses SRAM space, which can be convenient but
not desirable. The ideal way to use a Serial.print() or Serial.println() instruction is with the
use of the F() String wrapper around the literals. For example:
COPY
Serial.println(F("Something"));
Wrapping the String Something with the F() wrapper will move the Strings to Flash memory
only rather than to use SRAM space also. Using the F() wrapper can be observed as
offloading such data to Flash memory instead of SRAM. Flash memory is much more
spacious than SRAM, so it is better to use Flash memory space than SRAM, which will use
heap section. This does not mean that memory space will always be available, as Flash
memory does have limited space. It is not recommended to clog code with Serial.print() or
Serial.println() instructions, but use them where they most matter inside the code.
PROGMEM
Not only Strings occupy SRAM space, but global variables also take up quite a good amount
of SRAM space. As global and static variables are streamed into SRAM space and push the
heap memory section towards the stack. The space occupied by these variables streamed into
SRAM space will be saved at its location and will not be changing, meaning more of these
variables are created, they will use more space, and consequently, the system presenting
problems and issues due to poor memory management.
PROGMEM, which stands for Program Memory, can be used to store variable data into Flash
memory space, just as the F() wrapper described before, but the use of PROGMEM presents
one disadvantage: data read speed. Using RAM will provide a much faster data read speed,
but PROGMEM, as it uses Flash memory, will be slower than RAM, given the same data
size. Thus, it is essential to design code knowing which variables are crucial and which do
not or have a lower priority.
The use of PROGMEM in an AVR-based Arduino® board is shown in the example code
below:
COPY
Explain
#include <avr/pgmspace.h>
For ARM-based Arduino® board, to implement similar solution, we will need to use static
const over the variables.
COPY
static const int Variable = Data;
The usage differs in different levels summarized as following:
Namespace Level
At Namespace level, we are pointing at the variables and it is differed whether static is
declared or not. If declared, it infers that the variable is explicit static; on the other hand, it is
implicit static declaration.
Function Level
If it is declared within static, any type of applicable data that is to be managed will be
between function calls.
Class Level
On a Class level, static declaration will mean any type of applicable data that is handled will
be shared in between the instances.
Non-Dynamic Memory Allocation
Dynamic memory allocation is usually a suitable method if the RAM size of the system is big
enough to get around with; however, for microcontroller-based systems, such as embedded
systems, counting every Byte of RAM is not recommended.
Dynamic memory allocations cause heap fragmentation. With heap fragmentation, many
areas of RAM affected by it cannot be reused again, leaving dead Bytes that can be taken as
an advantage for other tasks. On top of it, when dynamic memory allocation proceeds to de-
allocate to free up the space, it does not necessarily reduce the heap size. So to avoid heap or
RAM fragmentation as much as possible, the following rules can be followed:
Stack memory is fragmentation-free and can be freed up thoroughly when the function
returns. Heap, in contrast, may not free up the space even though it was instructed to do so.
Using local variables will help to do this and try not to use dynamic memory allocation,
composed of different calls: malloc, calloc, realloc.
Reduced global and static data (if possible):
Meantime the code is running, memory area occupied by these data will not be freed up. The
data will not be modified as constant data takes up precious space.
Use short Strings/literals:
It is good to keep Strings/literals as short as possible. A single char takes one Byte of RAM,
so the shorter, the better memory space usage. This does not mean keeping it short and using
it in several different code areas is possible. Use it when required and keep it as short as
possible to spare RAM space for other tasks.
Arrays are also recommended to be at a minimum size. If it requires resizing the array, you
can always re-set the array size in code. It may be a tedious, also non-efficient method to
hard-code the array sizes. However, if the code utilizes small array sizes and less than three
arrays, it may suffice via manual resizing, knowing the requirements. An intelligent way to
do this is a resizable array with limited size. The tasks will use the array without going over
the size boundary. Thus it is suitable for extensive code. Although, the limit of the array size
must be analyzed and kept as small as possible.
Reserve Function
In tasks in code work with Strings that change in size depending on the operation outcome,
reserve() is the way to go. This function will help reserve buffer space and pre-allocate for a
String variable, changing its size and avoiding memory fragmentation. A String variable that
changes in its size could result from an int type variable wrapped to be used as a String, for
example.
COPY
// String_Variable is an String type variable
// Alloc_Size is the memory to be pre-allocated in number of Bytes with unsigned int type
String_Variable.reserve(Alloc_Size);
For more information about the reserve() function, visit Arduino Language Reference.
COPY
#define SERIAL_TX_BUFFER_SIZE 64
#define SERIAL_RX_BUFFER_SIZE 64
External libraries can usually be modified to optimize buffer sizes used for performing
specific tasks of the libraries.
Data types exist to ease data stream format and to be handled without making illegal access.
The illegal access in terms of data types are meant when the data is handled in the code with
incompatible format. So it is a good practice to not to abuse the the data type and use only
convenient types for every data bits. Rather, design and allocate memory carefully according
to the requirements, which will help to reserve some memory space if further designed tasks
needs extra space.
With EEPROM, it is crucial to know that write operation is limited. The read operation is
unlimited for EEPROM; however, the write operation is finite and usually capped at 100,000
cycles. Thus, it is essential to save only essential parameters for sensors or modules to work
with primarily unchanging data. Additionally, avoid implementing write operations into loops
to avoid constant write operations, these operations should be minimized while the system is
working.
Sometimes the developer would have to use the EEPROM as alternative storage for task
operations, but we know it will be impractical coding due to its size and behavior properties.
It is possible to use Flash memory to emulate the EEPROM to solve this. Thanks to the
FlashStorage library created by Chrisitan Maglie, it is possible to emulate the EEPROM by
using Flash memory.
The FlashStorage library will help you to use the Flash memory to emulate the EEPROM, but
of course, please remember the EEPROM's properties when using the library. As for
EEPROM, the Flash memory is also limited in the write cycles. With two new additional
functions stated in the library, EEPROM.commit() should not be called inside a loop
function; otherwise, it will wipe out the Flash memory's write operation cycles, thus losing
data retention ability.
8-bit AVR® Core documentation in the Microchip® Developer help site. Here you can find
detailed information of the 8-bit AVR® Central Processing Unit (CPU).
ARM architecture documentation site. Here you can find detailed information of the different
ARM processors. Check out the Cortex-M0+ and Cortex-M4 Technical Reference Manuals.
References
[1] S. F. Barrett and D. J. Pack, Microchip AVR® Microcontroller Primer: Programming and
Interfacing, Third Edition (Synthesis Lectures on Digital Circuits and Systems), Morgan &
Claypool, 2019.
[2] J. Y. Yiu, The Definitive Guide to Arm® Cortex®-M0 and Cortex-M0+ Processors,
Second ed., Newnes, 2015.
[3] J. Yiu, The Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors, Third
ed., Newnes, 2014.
A guide to EEPROM
Learn how to use EEPROM, short for electrically erasable programmable read-only memory,
on Arduino boards.
Last revision08/02/2024
The microcontroller on the Arduino boards have 512 bytes of EEPROM: memory whose
values are kept when the board is turned off (like a tiny hard drive).
Functions in the EEPROM class is automatically included with the platform for your board,
meaning you do not need to install any external libraries.
Hardware Required
All of the following boards have an EEPROM:
COPY
Explain
/*
* EEPROM Clear
*
* Sets all of the bytes of the EEPROM to 0.
* Please see eeprom_iteration for a more in depth
* look at how to traverse the EEPROM.
*
* This example code is in the public domain.
*/
#include <EEPROM.h>
void setup() {
// initialize the LED pin as an output.
pinMode(13, OUTPUT);
/***
Iterate through each byte of the EEPROM storage.
Larger AVR processors have larger EEPROM sizes, E.g:
- Arduino Duemilanove: 512 B EEPROM storage.
- Arduino Uno: 1 kB EEPROM storage.
- Arduino Mega: 4 kB EEPROM storage.
Rather than hard-coding the length, you should use the pre-provided length function.
This will make your code portable to all AVR processors.
***/
void loop() {
/** Empty loop. **/
}
EEPROM CRC
A CRC is a simple way of checking whether data has changed or become corrupted. This
example calculates a CRC value directly on the EEPROM values. This CRC is like a
signature and any change in the calculated CRC means a change in the stored data. The
purpose of this example is to highlight how the EEPROM object can be used just like an
array.
COPY
Explain
/***
A CRC is a simple way of checking whether data has changed or become corrupted.
The purpose of this example is to highlight how the EEPROM object can be used just like
an array.
***/
#include <Arduino.h>
#include <EEPROM.h>
void setup() {
//Start serial
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
Serial.println(EEPROM.length());
Serial.println(eeprom_crc(), HEX);
Serial.print("\n\nDone!");
}
void loop() {
/* Empty loop */
}
};
crc = crc_table[(crc ^ (EEPROM[index] >> 4)) & 0x0f] ^ (crc >> 4);
crc = ~crc;
return crc;
}
EEPROM Get
The purpose of this example is to show how the put and get methods provide a different
behaviour than write and read, that work on single bytes. Getting different variables from
EEPROM retrieve a number of bytes that is related to the variable datatype.
COPY
Explain
/***
eeprom_get example.
This sketch will run without it, however, the values shown
This may cause the serial object to print out a large string
loaded.
***/
#include <EEPROM.h>
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
EEPROM.get(eeAddress, f);
Serial.println(f, 3); //This may print 'ovf, nan' if the data inside the EEPROM is not a valid
float.
/***
***/
/***
***/
struct MyObject {
float field1;
byte field2;
char name[10];
};
void secondTest() {
int eeAddress = sizeof(float); //Move address to the next byte after float 'f'.
EEPROM.get(eeAddress, customVar);
Serial.println(customVar.field1);
Serial.println(customVar.field2);
Serial.println(customVar.name);
}
void loop() {
/* Empty loop */
}
EEPROM Iteration
The purpose of this example is to show how to go through the whole EEPROM memory
space with different approaches. The code provided doesn't run on its own but should be used
as a surce of code snippets to be used elsewhere.
COPY
Explain
/***
eeprom_iteration example.
***/
#include <EEPROM.h>
void setup() {
/***
***/
EEPROM[ index ] += 1;
/***
int index = 0;
EEPROM[ index ] += 1;
index++;
/***
***/
int idx = 0; //Used 'idx' to avoid name conflict with 'index' above.
do {
EEPROM[ idx ] += 1;
idx++;
void loop() {}
EEPROM Put
The purpose of this example is to show the EEPROM.put() method that writes data on
EEPROM using also the EEPROM.update() that writes data only if it is different from the
previous content of the locations to be written. The number of bytes written is related to the
datatype or custom structure of the variable to be written.
COPY
Explain
/***
eeprom_put example.
Also, this sketch will pre-set the EEPROM data for the
different.
***/
#include <EEPROM.h>
struct MyObject {
float field1;
byte field2;
char name[10];
};
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
//One simple call, with the address first and the object second.
EEPROM.put(eeAddress, f);
/** Put is designed for use with custom structures also. **/
//Data to store.
MyObject customVar = {
3.14f,
65,
"Working!"
};
eeAddress += sizeof(float); //Move address to the next byte after float 'f'.
EEPROM.put(eeAddress, customVar);
Serial.print("Written custom data type! \n\nView the example sketch eeprom_get to see how
you can retrieve the values!");
}
void loop() {
/* Empty loop */
}
EEPROM Read
This example illustrates how to read the value of each byte EEPROM using the
EEPROM.read() function, and how to print those values to the serial window of the Arduino
Software (IDE).
COPY
Explain
/*
* EEPROM Read
*
* Reads the value of each byte of the EEPROM and prints it
* to the computer.
* This example code is in the public domain.
*/
#include <EEPROM.h>
void setup() {
// initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
}
void loop() {
// read a byte from the current address of the EEPROM
value = EEPROM.read(address);
Serial.print(address);
Serial.print("\t");
Serial.print(value, DEC);
Serial.println();
/***
Advance to the next address, when at the end restart at the beginning.
Rather than hard-coding the length, you should use the pre-provided length function.
This will make your code portable to all AVR processors.
***/
address = address + 1;
if (address == EEPROM.length()) {
address = 0;
}
/***
As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an
EEPROM address is also doable by a bitwise and of the length - 1.
delay(500);
}
EEPROM Update
The purpose of this example is to show the EEPROM.update() method that writes data only if
it is different from the previous content of the locations to be written. This solution may save
execution time because every write operation takes 3.3 ms; the EEPROM has also a limit of
100,000 write cycles per single location, therefore avoiding rewriting the same value in any
location will increase the EEPROM overall life.
COPY
Explain
/***
EEPROM Update method
#include <EEPROM.h>
/** the current address in the EEPROM (i.e. which byte we're going to write to next) **/
int address = 0;
void setup() {
/** Empty setup **/
}
void loop() {
/***
need to divide by 4 because analog inputs range from
0 to 1023 and each byte of the EEPROM can only hold a
value from 0 to 255.
***/
int val = analogRead(0) / 4;
/***
Update the particular EEPROM cell.
these values will remain there when the board is
turned off.
***/
EEPROM.update(address, val);
/***
The function EEPROM.update(address, val) is equivalent to the following:
/***
Advance to the next address, when at the end restart at the beginning.
Rather than hard-coding the length, you should use the pre-provided length function.
This will make your code portable to all AVR processors.
***/
address = address + 1;
if (address == EEPROM.length()) {
address = 0;
}
/***
As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an
EEPROM address is also doable by a bitwise and of the length - 1.
delay(100);
}
EEPROM Write
This example illustrates how to store values read from analog input 0 into the EEPROM
using the EEPROM.write() function. These values will stay in the EEPROM when the board
is turned off and may be retrieved later by another sketch.
COPY
Explain
/*
* EEPROM Write
*/
#include <EEPROM.h>
/** the current address in the EEPROM (i.e. which byte we're going to write to next) **/
int addr = 0;
void setup() {
void loop() {
/***
AuthorArduino
Last revision08/02/2024
This article was revised on 2021/11/18 by Karl Söderby.
This guide collects compatible hardware and great code examples that you can use if you
want to get started with Secure Digital (SD) cards.
The examples in this guide comes from the SD Library, which originally is based on SdFat
by William Greiman.
MKR Zero
MKR IoT Carrier
MKR MEM Shield
MKR SD Proto Shield
MKR ENV Shield
MKR Ethernet Shield
Arduino Education Shield
Circuit
Here is an example of how to insert an SD card into the MKR Zero board. None of the
examples below requires any additional circuit.
Examples
Below are a series of examples
In setup(), create a new file with SD.open() named "test.txt". FILE_WRITE enables read and
write access to the file, starting at the end. If a file "test.txt" was already on the card, that file
would be opened.
Name the instance of the opened file "myFile".
Once opened, use myFile.println() to write a string to the card, followed by a carriage return.
Once the content is written, close the file.
Again, open the file with SD.open(). Once opened, ask the Arduino to read the contents of the
file with SD.read() and send them over the serial port. After all the contents of the file are
read, close the file with SD.close().
Note that pin 4 is default Chip Select (CS) pin for most boards. To set CS for MKR Zero, you
can use 28 instead of 4, alt. use the SDCARD_SS_PIN definition.
COPY
Explain
/*
SD card read/write
This example shows how to read and write data to and from an SD card file
The circuit:
SD card attached to SPI bus as follows:
** MOSI - pin 11
** MISO - pin 12
** CLK - pin 13
** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)
*/
#include <SPI.h>
#include <SD.h>
File myFile;
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.print("Initializing SD card...");
if (!SD.begin(4)) {
Serial.println("initialization failed!");
while (1);
}
Serial.println("initialization done.");
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
myFile = SD.open("test.txt", FILE_WRITE);
void loop() {
// nothing happens after setup
}
Card Information
This example shows how to read information about a SD card. The example reports volume
type, free space and other information using the SD library, sending it over the serial port.
COPY
Explain
/*
SD card test
This example shows how use the utility libraries on which the'
Very useful for testing a card when you're not sure whether its working or not.
Pin numbers reflect the default SPI pins for Uno and Nano models
The circuit:
by Limor Fried
by Tom Igoe
*/
// include the SD library:
#include <SPI.h>
#include <SD.h>
Sd2Card card;
SdVolume volume;
SdFile root;
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
Serial.print("\nInitializing SD card...");
if (!card.init(SPI_HALF_SPEED, chipSelect)) {
Serial.println("* did you change the chipSelect pin to match your shield or module?");
while (1);
} else {
Serial.println();
switch (card.type()) {
case SD_CARD_TYPE_SD1:
Serial.println("SD1");
break;
case SD_CARD_TYPE_SD2:
Serial.println("SD2");
break;
case SD_CARD_TYPE_SDHC:
Serial.println("SDHC");
break;
default:
Serial.println("Unknown");
if (!volume.init(card)) {
while (1);
Serial.print("Clusters: ");
Serial.println(volume.clusterCount());
Serial.println(volume.blocksPerCluster());
Serial.println(volume.blocksPerCluster() * volume.clusterCount());
Serial.println();
uint32_t volumesize;
Serial.println(volume.fatType(), DEC);
volumesize /= 2; // SD card blocks are always 512 bytes (2 blocks are 1KB)
Serial.println(volumesize);
volumesize /= 1024;
Serial.println(volumesize);
Serial.println((float)volumesize / 1024.0);
Serial.println("\nFiles found on the card (name, date and size in bytes): ");
root.openRoot(volume);
root.close();
}
void loop(void) {
}
Please note: the cluster size is defined at format time by the user and has some default values
that can be changed by users following some rules. In the sketch above we are using the
default size for a block that is set at 512 bytes by standards. This value is not the cluster size:
that is calculated as the number of blocks per cluster. You may find more in depth
information about cluster sizes in this article.
Dump File
This example shows how to read a file from a SD card using the SD library and send it over
the serial port.
On the SD card, there is a file named "datalog.txt". In the loop(), the file is opened when
calling SD.open(). To send the file serially to a computer, use Serial.print(), reading the
contents of the file with SD.read().
COPY
Explain
/*
This example shows how to read a file from the SD card using the
The circuit:
** SDO - pin 11
** SDI - pin 12
** CLK - pin 13
by Limor Fried
by Tom Igoe
*/
#include <SD.h>
void setup() {
Serial.begin(9600);
// wait for Serial Monitor to connect. Needed for native USB port boards only:
while (!Serial);
Serial.print("Initializing SD card...");
if (!SD.begin(chipSelect)) {
Serial.println("3. did you change the chipSelect pin to match your shield or module?");
Serial.println("Note: press reset or reopen this serial monitor after fixing your issue!");
while (true);
Serial.println("initialization done.");
// open the file. note that only one file can be open at a time,
if (dataFile) {
while (dataFile.available()) {
Serial.write(dataFile.read());
dataFile.close();
else {
}
}
void loop() {
}
File Management
This example shows how to create and destroy a file on a SD card.
In the setup(), open a new file with SD.open() named "example.txt". FILE_WRITE enables
read and write access to the file, starting at the end. In this example though, immediately
close the file by calling myFile.close().
After checking to make sure the file exists with SD.exists(), delete the file from the card with
SD.remove.
COPY
Explain
/*
SD card basic file example
** SDO - pin 11
** SDI - pin 12
** CLK - pin 13
by David A. Mellis
by Tom Igoe
*/
#include <SD.h>
File myFile;
void setup() {
Serial.begin(9600);
// wait for Serial Monitor to connect. Needed for native USB port boards only:
while (!Serial);
Serial.print("Initializing SD card...");
if (!SD.begin(10)) {
Serial.println("initialization failed!");
while (1);
}
Serial.println("initialization done.");
if (SD.exists("example.txt")) {
Serial.println("example.txt exists.");
} else {
Serial.println("Creating example.txt...");
myFile.close();
if (SD.exists("example.txt")) {
Serial.println("example.txt exists.");
} else {
Serial.println("Removing example.txt...");
SD.remove("example.txt");
if (SD.exists("example.txt")) {
Serial.println("example.txt exists.");
} else {
}
}
void loop() {
The main loop() does nothing because the function that prints out the file directory of "/" of
the SD card is called from the setup(). This because we need to see it just once.
The printDirectory function scans through the list of entries and prints on serial every file and
directory present. For files the size is printed as well.
COPY
Explain
/*
Listfiles
The circuit:
** SDO - pin 11
** SDI - pin 12
** CLK - pin 13
by David A. Mellis
by Tom Igoe
by Scott Fitzgerald
by Tom Igoe
This example code is in the public domain.
*/
#include <SD.h>
AuthorArduino
Last revision08/02/2024
Bit masks are used to access specific bits in a byte of data. This is often useful as a method of
iteration, for example when sending a byte of data serially out a single pin. In this example
the pin needs to change it's state from high to low for each bit in the byte to be transmitted.
This is accomplished using what are known as bitwise operations and a bit mask.
Bitwise operations perform logical functions that take affect on the bit level. Standard bitwise
operations include AND (&) OR (|) Left Shift (<<) and Right Shift (>>).
The AND (&) operator will result in a 1 at each bit position where both input values were 1.
For example:
COPY
Explain
x: 10001101
y: 01010111
x & y: 00000101
The OR (|) operator (also known as Inclusive Or) will result in a 1 at each bit position where
either input values were 1. For example:
COPY
Explain
x: 10001101
y: 01010111
x | y: 11011111
The Left Shift (<<) operator will shift a value to the left the specified number of times. For
example:
COPY
Explain
y = 1010
x = y << 1
yields: x = 10100
All the bits in the byte get shifted one position to the left and the bit on the left end drops off.
The Right Shift (>>) operator works identically to left shift except that it shifts the value to
the right the specified number of times For example:
COPY
Explain
y = 1010
x = y >> 1
yields: x = 0101
All the bits in the byte get shifted one position to the right and the bit on the right end drops
off.
For a practical example, let's take the value 170, binary 10101010. To pulse this value out of
pin 7 the code might look as follows:
COPY
Explain
byte transmit = 7; //define our transmit pin
byte data = 170; //value to transmit, binary 10101010
byte mask = 1; //our bitmask
byte bitDelay = 100;
void setup()
{
pinMode(transmit,OUTPUT);
}
void loop()
{
for (mask = 00000001; mask>0; mask <<= 1) { //iterate through bit mask
digitalWrite(transmit,HIGH); // send 1
digitalWrite(transmit,LOW); // send 0
delayMicroseconds(bitDelay); //delay
}
}
Here we use a FOR loop to iterate through a bit mask value, shifting the value one position
left each time through the loop. In this example we use the <<= operator which is exactly like
the << operator except that it compacts the statement
COPY
Explain
00000001
& 10101010
________
00000000
And our output pin gets set to 0. Second time through the loop the mask = 00000010, so our
operation looks like:
COPY
Explain
00000010
& 10101010
________
00000010
And our output pin gets set to 1. The loop will continue to iterate through each bit in the mask
until the 1 gets shifted left off the end of the 8 bits and our mask =0. Then all 8 bits have been
sent and our loop exits.
AuthorPaul Badger, Alexandre Quessy, Michael Smith, Samantha Lagestee, Dan Thompson
Last revision08/02/2024
This article was revised on 2022/09/28 by Hannes Siebeneicher.
This article highlights different approaches to making sounds and even entire songs with an
Arduino. In 2013 Brett Hagman created the tone() library which is a good starting point for
creating different types of sounds using an Arduino. As the examples in this article are
gathered from the Arduino playground and were mostly created before 2013 a lot of steps are
still done manually, which can be skipped when using the tone() library.
The examples are nevertheless still relevant as they explain some basic concepts of
generating tone frequencies, interpolation and even provide you with some songs to try out. If
you want to see an example for a simple melody using the tone() library and familiarize
yourself with the concept of external sound data files, you can check out this example.
Most sketches in this article use pin 8 as output for the piezo buzzer or speaker which means
you only need to connect your components a shown below and try out the different examples
by uploading them to your Arduino. Only the PCMAudio example uses pin 11 as it is making
use of PWM.
Hardware Required
Arduino board
piezo buzzer or a speaker
hook-up wires
Circuit
circuit
Schematic
schematic
Basics
Most times a piezo buzzer is used to produce sounds with an Arduino. When voltage is
applied to a piezoelectric ceramic material it causes it to vibrate rapidly, resulting in the
generation of sound waves. Every wave has an associated property called frequency which
measures how many cycles happen every second. This unit of cycles is called Hertz (Hz).
E.g., A middle C on the piano has a frequency of 262 Hz which means that the air oscillates
back and forth 262 times every second.
Another property of a wave is its period, which equals to one divided by the frequency,
measuring the length and time of the wave. So, for that middle C on the piano the cycle
repeats every 3.8 milliseconds. While a normal pure tone is a sine wave, it is much easier to
create a square wave using an Arduino by turning the pin on, waiting for a certain amount of
time, then turning the pin off and waiting again.
Freqout
The following example was created by Paul Badger in 2007. It shows a simple tone
generation function generating square waves of arbitrary frequency and duration. The
program also includes a top-octave lookup table & transportation function.
COPY
Explain
#include <math.h> // requires an Atmega168 chip
float noteval;
//rhythm values
int wh = 1024;
int h = 512;
int dq = 448;
int q = 256;
int qt = 170;
int de = 192;
int e = 128;
int et = 85;
int dsx = 96;
int sx = 64;
int thx = 32;
float majScale[] = {
A, B, CS, D, E, FS, GS, A2, B2, C2S, D2, E2, F2S, G2S, A3};
void setup() {
Serial.begin(9600);
}
void loop(){
for(i= 0; i<=11; i++){
ps = (float)i / 12; // choose new transpose interval every loop
for(x= 0; x<=15; x++){
noteval = (majScale[x] / oct4) * pow(2,ps); // transpose scale up 12 tones
// pow function generates transposition
// eliminate " * pow(2,ps) " to cut out transpose routine
dur = 100;
freqout((int)noteval, dur);
delay(10);
}
}
}
}
Duration extension
In the example below some minor tweaks have been made, mostly changing the array to have
durations and a sentinel was added to mark the end. The example shown above remains as it
shows a great simplistic structure.
COPY
Explain
float EIGHTH = 1;
float QUARTER = 2;
float DOTTED_QUARTER =3;
float HALF = 4;
float ETERNITY =-1;
float TEMPO = 150;
float majScale[] = {
A,QUARTER, B,QUARTER, CS,QUARTER, D,QUARTER, E,QUARTER,
FS,QUARTER, GS,QUARTER, A2,QUARTER, B2,QUARTER,
C2S,QUARTER, D2,QUARTER, E2,QUARTER, F2S,QUARTER, G2S,QUARTER,
A3,QUARTER, REST,ETERNITY
};
float odeToJoy[] = {
F2S,QUARTER, F2S,QUARTER, G2,QUARTER, A3,QUARTER, A3,QUARTER,
G2,QUARTER, F2S,QUARTER, E2,QUARTER, D2,QUARTER,
D2,QUARTER, E2,QUARTER, F2S,QUARTER, F2S,DOTTED_QUARTER,
E2,EIGHTH, E2,HALF, F2S,QUARTER, F2S,QUARTER, G2,QUARTER,
A3,QUARTER, A3,QUARTER,G2,QUARTER, F2S,QUARTER, E2,QUARTER,
D2,QUARTER, D2,QUARTER, E2,QUARTER, F2S,QUARTER,
E2,DOTTED_QUARTER,
D2,EIGHTH, D2,HALF, E2,QUARTER, E2,QUARTER, F2S,QUARTER,
D2,QUARTER, E2,QUARTER, F2S,EIGHTH, G2,EIGHTH, F2S,QUARTER,
D2,QUARTER,
E2,QUARTER, F2S,EIGHTH, G2,EIGHTH, F2S,QUARTER, E2,QUARTER,
D2,QUARTER, E2,QUARTER, A,QUARTER, REST,ETERNITY
};
Smoothstep
This example is made by Dan Thompson in 2009 for smooth interpolation between two
values. Smoothstep is a common formula used for many different applications such as
Animation and Audio. This sketch includes a Serial Printout to help you visualize the
formula. Visit danthompsonsblog.blogspot.com for the full smoothstep tutorial as well as
many others. For a comprehensive overview of interpolation as well as some great Tips and
Tricks visit this page.
Code
COPY
Explain
///////////////////////////////////////
// Smoothstep Interpolation Example //
///////////////////////////////////////
void setup() {
Serial.begin(9600); //establish serial connection for debugging
}
void loop()
{
if (j < N) // Keep looping until we hit the pre-defined max number
// of steps
{
v = j / N; // Iteration divided by the number of steps.
v = SMOOTHSTEP(v); // Run the smoothstep expression on v.
X = (B _ v) + (A _ (1 - v)); // Run the linear interpolation expression using the current
//smoothstep result.
for ( i=0; i < X ; i++) // This loop could the relevant code for each time your
//motor steps.
{
Serial.print("1"); //Prints the number "1" for each step.
}
Serial.print(" "); //Puts a space between each line of steps and their
//corresponding float value
Serial.println(X); // prints the soothstepped value
Serial.println("CLICK!!!"); // this could be where you trigger your timelapse shutter
j++; // Increments j by 1.
}
}
PCMAudio
The following example was created by Michael Smith and is the precursor for the PCM
library created by David Mellis. It plays 8-bit PCM audio on pin 11 using pulse-width
modulation (PWM). It uses two timers. The first changes the sample value 8000 times a
second. The second holds pin 11 high for 0-255 ticks out of a 256-tick cycle, depending on
the sample value. The second timer repeats 62500 times per second (16000000 / 256), which
is much faster than the playback rate (8000 Hz), so it almost sounds halfway decent, just
really quiet on a PC speaker.
Takes over Timer 1 (16-bit) for the 8000 Hz timer. This breaks PWM (analogWrite()) for
Arduino pins 9 & 10. It then takes Timer 2 (8-bit) for the pulse width modulation, breaking
the PWM for pins 11 & 13.
References:
http://tet.pub.ro/ (PDF).
https://www.evilmadscientist.com/article.php/avrdac
https://www.gamedev.net/reference/articles/article442.asp
Code
COPY
Explain
#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
/*
* The audio data needs to be unsigned, 8-bit, 8000 Hz, and small enough
* to fit in flash. 10000-13000 samples is about the limit.
*
* sounddata.h should look like this:
* const int sounddata_length=10000;
* const unsigned char sounddata_data[] PROGMEM = { ..... };
*
* You can use wav2c from GBA CSS:
* https://thieumsweb.free.fr/english/gbacss.html
* Then add "PROGMEM" in the right place. I hacked it up to dump the samples
* as unsigned rather than signed, but it shouldn't matter.
*
* https://musicthing.blogspot.com/2005/05/tiny-music-makers-pt-4-mac-startup.html
* mplayer -ao pcm macstartup.mp3
* sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
* sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
* wav2c macstartup-cut.wav sounddata.h sounddata
*
* (starfox) nb. under sox 12.18 (distributed in CentOS 5), i needed to run
* the following command to convert my wav file to the appropriate format:
* sox audiodump.wav -c 1 -r 8000 -u -b macstartup-8000.wav
*/
#include "sounddata.h"
void stopPlayback()
{
// Disable playback per-sample interrupt.
TIMSK1 &= ~_BV(OCIE1A);
// Disable the per-sample timer completely.
TCCR1B &= ~_BV(CS10);
digitalWrite(speakerPin, LOW);
}
++sample;
}
void startPlayback()
{
pinMode(speakerPin, OUTPUT);
if(speakerPin==11){
// Do non-inverting PWM on pin OC2A (p.155)
// On the Arduino this is pin 11.
TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler (p.158)
TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
cli();
// No prescaler (p.134)
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
sample = 0;
sei();
}
void setup()
{
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH);
startPlayback();
}
void loop()
{
while (true);
}
The above sketch also requires the sounddata.h file which you can find below:
COPY
Explain
// sounddata sound made by wav2c
// (wav2c modified to use unsigned samples)
Code
COPY
Explain
/* Rick Roll Code
AUTHOR: Samantha Lagestee
Copyright 2017 samilagestee at gmail dot com
DISCLAIMER: The song "Never Gonna Give You Up" by Rick Astley
is not the creative property of the author. This code simply
plays a Piezo buzzer rendition of the song.
*/
#define rest -1
int song1_intro_melody[] =
{c5s, e5f, e5f, f5, a5f, f5s, f5, e5f, c5s, e5f, rest, a4f, a4f};
int song1_intro_rhythmn[] =
{6, 10, 6, 6, 1, 1, 1, 1, 6, 10, 4, 2, 10};
// Parts 3 or 5 (Verse 1)
int song1_verse1_melody[] =
{ rest, c4s, c4s, c4s, c4s, e4f, rest, c4, b3f, a3f,
rest, b3f, b3f, c4, c4s, a3f, a4f, a4f, e4f,
rest, b3f, b3f, c4, c4s, b3f, c4s, e4f, rest, c4, b3f, b3f, a3f,
rest, b3f, b3f, c4, c4s, a3f, a3f, e4f, e4f, e4f, f4, e4f,
c4s, e4f, f4, c4s, e4f, e4f, e4f, f4, e4f, a3f,
rest, b3f, c4, c4s, a3f, rest, e4f, f4, e4f
};
int song1_verse1_rhythmn[] =
{ 2, 1, 1, 1, 1, 2, 1, 1, 1, 5,
1, 1, 1, 1, 3, 1, 2, 1, 5,
1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 3,
1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 4,
5, 1, 1, 1, 1, 1, 1, 1, 2, 2,
2, 1, 1, 1, 3, 1, 1, 1, 3
};
char\* lyrics_verse1[] =
{ "We're ", "no ", "strangers ", "", "to ", "love ", "", "\r\n",
"You ", "know ", "the ", "rules ", "and ", "so ", "do ", "I\r\n",
"A ", "full ", "commitment's ", "", "", "what ", "I'm ", "thinking ", "", "of", "\r\n",
"You ", "wouldn't ", "", "get ", "this ", "from ", "any ", "", "other ", "", "guy\r\n",
"I ", "just ", "wanna ", "", "tell ", "you ", "how ", "I'm ", "feeling", "\r\n",
"Gotta ", "", "make ", "you ", "understand", "", "\r\n"
};
// Parts 4 or 6 (Chorus)
int song1_chorus_melody[] =
{ b4f, b4f, a4f, a4f,
f5, f5, e5f, b4f, b4f, a4f, a4f, e5f, e5f, c5s, c5, b4f,
c5s, c5s, c5s, c5s,
c5s, e5f, c5, b4f, a4f, a4f, a4f, e5f, c5s,
b4f, b4f, a4f, a4f,
f5, f5, e5f, b4f, b4f, a4f, a4f, a5f, c5, c5s, c5, b4f,
c5s, c5s, c5s, c5s,
c5s, e5f, c5, b4f, a4f, rest, a4f, e5f, c5s, rest
};
int song1_chorus_rhythmn[] =
{ 1, 1, 1, 1,
3, 3, 6, 1, 1, 1, 1, 3, 3, 3, 1, 2,
1, 1, 1, 1,
3, 3, 3, 1, 2, 2, 2, 4, 8,
1, 1, 1, 1,
3, 3, 6, 1, 1, 1, 1, 3, 3, 3, 1, 2,
1, 1, 1, 1,
3, 3, 3, 1, 2, 2, 2, 4, 8, 4
};
char* lyrics_chorus[] =
{ "Never ", "", "gonna ", "", "give ", "you ", "up\r\n",
"Never ", "", "gonna ", "", "let ", "you ", "down", "", "\r\n",
"Never ", "", "gonna ", "", "run ", "around ", "", "", "", "and ", "desert ", "", "you\r\n",
"Never ", "", "gonna ", "", "make ", "you ", "cry\r\n",
"Never ", "", "gonna ", "", "say ", "goodbye ", "", "", "\r\n",
"Never ", "", "gonna ", "", "tell ", "a ", "lie ", "", "", "and ", "hurt ", "you\r\n"
};
void setup()
{
pinMode(piezo, OUTPUT);
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
Serial.begin(9600);
flag = true;
a = 4;
b = 0;
c = 0;
}
void loop()
{
// edit code here to define play conditions
/*
if (CONDITION 1) { // play
flag = true;
}
else if (CONDITION2) { // pause
flag = false;
}
*/
void play() {
int notelength;
if (a == 1 || a == 2) { // Intro
// intro
notelength = beatlength * song1_intro_rhythmn[b];
if (song1_intro_melody[b] > 0) { // if not a rest, play note
digitalWrite(led, HIGH);
tone(piezo, song1_intro_melody[b], notelength);
}
b++;
if (b >= sizeof(song1_intro_melody) / sizeof(int)) {
a++;
b = 0;
c = 0;
}
} else if (a == 3 || a == 5) { // Verse 1
// verse
notelength = beatlength * 2 * song1_verse1_rhythmn[b];
if (song1_verse1_melody[b] > 0) {
digitalWrite(led, HIGH);
Serial.print(lyrics_verse1[c]);
tone(piezo, song1_verse1_melody[b], notelength);
c++;
}
b++;
if (b >= sizeof(song1_verse1_melody) / sizeof(int)) {
a++;
b = 0;
c = 0;
}
} else if (a == 4 || a == 6) { //chorus
// chorus
notelength = beatlength * song1_chorus_rhythmn[b];
if (song1_chorus_melody[b] > 0) {
digitalWrite(led, HIGH);
Serial.print(lyrics_chorus[c]);
tone(piezo, song1_chorus_melody[b], notelength);
c++;
}
b++;
if (b >= sizeof(song1_chorus_melody) / sizeof(int)) {
Serial.println("");
a++;
b = 0;
c = 0;
}
}
Code
COPY
Explain
/\*
int ledPin = 9;
int speakerOut = 8;
byte beat = 0;
int MAXCOUNT = 32;
float TEMPO_SECONDS = 0.2;
byte statePin = LOW;
byte period = 0;
int i, timeUp;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(speakerOut, OUTPUT);
}
void loop() {
digitalWrite(speakerOut, LOW);
for (beat = 0; beat < MAXCOUNT; beat++) {
statePin = !statePin;
digitalWrite(ledPin, statePin);
timeUp = timeUpDown[song[beat]];
}
digitalWrite(speakerOut, LOW);
delay(1000);
}
Improved version
COPY
Explain
/\*
/_ our song. Each number pair is a MIDI note and a note symbol. _/
/_ Symbols are 1 for whole, -1 for dotted whole, 2 for half, _/
/_ -2 for dotted half, 4 for quarter, -4 for dotted quarter, etc. _/
Often when programming in the Arduino environment (or on any computer, for that matter),
the ability to manipulate individual bits will become useful or even necessary. Here are some
situations where bit math can be helpful:
In the familiar decimal system (base ten), a number like 572 means 5*10^2 + 7*10^1 +
2*10^0. Likewise, in binary a number like 11010 means 1*2^4 + 1*2^3 + 0*2^2 + 1*2^1 +
0*2^0 \= 16 + 8 + 2 = 26.
It is crucial that you understand how the binary system works in order to follow the
remainder of this tutorial. If you need help in this area, one good place to start is the
Wikipedia article on the binary system.
Arduino allows you to specify binary numbers by prefixing them with 0b, e.g., 0b11 == 3.
For legacy reasons, it also defines the constants B0 through B11111111, which can be used in
the same way.
Bitwise AND
The bitwise AND operator in C++ is a single ampersand, &, used between two other integer
expressions. Bitwise AND operates on each bit position of the surrounding expressions
independently, according to this rule: if both input bits are 1, the resulting output is 1,
otherwise the output is 0. Another way of expressing this is:
COPY
0 & 0 == 0
0 & 1 == 0
1 & 0 == 0
1 & 1 == 1
In Arduino, the type int is a 16-bit value, so using & between two int expressions causes 16
simultaneous AND operations to occur. In a code fragment like:
COPY
int a = 92; // in binary: 0000000001011100
int b = 101; // in binary: 0000000001100101
int c = a & b; // result: 0000000001000100, or 68 in decimal.
Each of the 16 bits in a and b are processed by using the bitwise AND, and all 16 resulting
bits are stored in c, resulting in the value 01000100 in binary, which is 68 in decimal.
One of the most common uses of bitwise AND is to select a particular bit (or bits) from an
integer value, often called masking. For example, if you wanted to access the least significant
bit in a variable x, and store the bit in another variable y, you could use the following code:
COPY
int x = 5; // binary: 101
int y = x & 1; // now y == 1
x = 4; // binary: 100
y = x & 1; // now y == 0
Bitwise OR
The bitwise OR operator in C++ is the vertical bar symbol, |. Like the & operator, | operates
independently each bit in its two surrounding integer expressions, but what it does is different
(of course). The bitwise OR of two bits is 1 if either or both of the input bits is 1, otherwise it
is 0. In other words:
COPY
0 | 0 == 0
0 | 1 == 1
1 | 0 == 1
1 | 1 == 1
Here is an example of the bitwise OR used in a snippet of C++ code:
COPY
int a = 92; // in binary: 0000000001011100
int b = 101; // in binary: 0000000001100101
int c = a | b; // result: 0000000001111101, or 125 in decimal.
Bitwise OR is often used to make sure that a given bit is turned on (set to 1) in a given
expression. For example, to copy the bits from a into b, while making sure the lowest bit is
set to 1, use the following code:
COPY
b = a | 1;
Bitwise XOR
There is a somewhat unusual operator in C++ called bitwise exclusive OR, also known as
bitwise XOR. (In English this is usually pronounced "eks-or".) The bitwise XOR operator is
written using the caret symbol ^. This operator is similar to the bitwise OR operator |, except
that it evaluates to 1 for a given position when exactly one of the input bits for that position is
1. If both are 0 or both are 1, the XOR operator evaluates to 0 :
COPY
0 ^ 0 == 0
0 ^ 1 == 1
1 ^ 0 == 1
1 ^ 1 == 0
Another way to look at bitwise XOR is that each bit in the result is a 1 if the input bits are
different, or 0 if they are the same.
COPY
int x = 12; // binary: 1100
int y = 10; // binary: 1010
int z = x ^ y; // binary: 0110, or decimal 6
The ^ operator is often used to toggle (i.e. change from 0 to 1, or 1 to 0) some of the bits in an
integer expression while leaving others alone. For example:
COPY
y = x ^ 1; // toggle the lowest bit in x, and store the result in y.
Bitwise NOT
The bitwise NOT operator in C++ is the tilde character ~. Unlike & and |, the bitwise NOT
operator is applied to a single operand to its right. Bitwise NOT changes each bit to its
opposite: 0 becomes 1, and 1 becomes 0. For example:
COPY
int a = 103; // binary: 0000000001100111
int b = ~a; // binary: 1111111110011000 = -104
You might be surprised to see a negative number like -104 as the result of this operation. This
is because the highest bit in an int variable is the so-called sign bit. If the highest bit is 1, the
number is interpreted as negative. This encoding of positive and negative numbers is referred
to as two's complement. For more information, see the Wikipedia article on two's
complement.
As an aside, it is interesting to note that for any integer x, ~x is the same as -x-1.
At times, the sign bit in a signed integer expression can cause some unwanted surprises, as
we shall see later.
COPY
int a = 5; // binary: 0000000000000101
int b = a << 3; // binary: 0000000000101000, or 40 in decimal
int c = b >> 3; // binary: 0000000000000101, or back to 5 like we started with
When you shift a value x by y bits (x << y), the leftmost y bits in x are lost, literally shifted
out of existence:
COPY
int a = 5; // binary: 0000000000000101
int b = a << 14; // binary: 0100000000000000 - the first 1 in 101 was discarded
If you are certain that none of the ones in a value are being shifted into oblivion, a simple
way to think of the left-shift operator is that it multiplies the left operand by 2 raised to the
right operand power. For example, to generate powers of 2, the following expressions can be
employed:
COPY
Explain
1 << 0 == 1
1 << 1 == 2
1 << 2 == 4
1 << 3 == 8
...
1 << 8 == 256
1 << 9 == 512
1 << 10 == 1024
...
When you shift x right by y bits (x >> y), and the highest bit in x is a 1, the behavior depends
on the exact data type of x. If x is of type int, the highest bit is the sign bit, determining
whether x is negative or not, as we have discussed above. In that case, the sign bit is copied
into lower bits, for esoteric historical reasons:
COPY
int x = -16; // binary: 1111111111110000
int y = x >> 3; // binary: 1111111111111110
This behavior, called sign extension, is often not the behavior you want. Instead, you may
wish zeros to be shifted in from the left. It turns out that the right shift rules are different for
unsigned int expressions, so you can use a typecast to suppress ones being copied from the
left:
COPY
int x = -16; // binary: 1111111111110000
int y = unsigned(x) >> 3; // binary: 0001111111111110
If you are careful to avoid sign extension, you can use the right-shift operator >> as a way to
divide by powers of 2. For example:
COPY
int x = 1000;
int y = x >> 3; // integer division of 1000 by 8, causing y = 125.
Assignment Operators
Often in programming, you want to operate on the value of a variable x and store the
modified value back into x. In most programming languages, for example, you can increase
the value of a variable x by 7 using the following code:
COPY
x = x + 7; // increase x by 7
Because this kind of thing occurs so frequently in programming, C++ provides a shorthand
notation in the form of specialized assignment operators. The above code fragment can be
written more concisely as:
COPY
x += 7; // increase x by 7
It turns out that bitwise AND, bitwise OR, left shift, and right shift, all have shorthand
assignment operators. Here is an example:
COPY
Explain
int x = 1; // binary: 0000000000000001
x <<= 3; // binary: 0000000000001000
x |= 3; // binary: 0000000000001011 - because 3 is 11 in binary
x &= 1; // binary: 0000000000000001
x ^= 4; // binary: 0000000000000101 - toggle using binary mask 100
x ^= 4; // binary: 0000000000000001 - toggle with mask 100 again
There is no shorthand assignment operator for the bitwise NOT operator ~; if you want to
toggle all the bits in x, you need to do this:
COPY
x = ~x; // toggle all bits in x and store back in x
A word of caution: bitwise operators vs. boolean operators
It is very easy to confuse the bitwise operators in C++ with the boolean operators. For
instance, the bitwise AND operator & is not the same as the boolean AND operator &&, for
two reasons:
They don't calculate numbers the same way. Bitwise & operates independently on each bit in
its operands, whereas && converts both of its operands to a boolean value (true\==1 or false\
==0), then returns either a single true or false value. For example, 4 & 2 == 0, because 4 is
100 in binary and 2 is 010 in binary, and none of the bits are 1 in both integers. However, 4
&& 2 == true, and true numerically is equal to 1. This is because 4 is not 0, and 2 is not 0, so
both are considered as boolean true values.
Bitwise operators always evaluate both of their operands, whereas boolean operators use so-
called short-cut evaluation. This matters only if the operands have side-effects, such as
causing output to occur or modifying the value of something else in memory. Here is an
example of how two similar looking lines of code can have very different behavior:
COPY
Explain
int fred (int x)
{
Serial.print ("fred ");
Serial.println (x, DEC);
return x;
}
COPY
void setup()
{
Serial.begin (9600);
}
COPY
Explain
void loop()
{
delay(1000); // wait 1 second, so output is not flooded with serial data!
int x = fred(0) & fred(1);
}
If you compile and upload this program, and then monitor the serial output from the Arduino
GUI, you will see the following lines of text repeated every second:
COPY
fred 0
fred 1
This is because both fred(0) and fred(1) are called, resulting in the generated output, the
return values 0 and 1 are bitwise-ANDed together, storing 0 in x. If you edit the line
COPY
int x = fred(0) & fred(1);
and replace the bitwise & with its boolean counterpart &&,
COPY
int x = fred(0) && fred(1);
and compile, upload, and run the program again, you may be surprised to see only a single
line of text repeated every second in the serial monitor window:
COPY
fred 0
Why does this happen? This is because boolean && is using a short-cut: if its left operand is
zero (a.k.a. false), it is already certain that the result of the expression will be false, so there is
no need to evaluate the right operand. In other words, the line of code int x = fred(0) &&
fred(1); is identical in meaning to:
COPY
Explain
int x;
if (fred(0) == 0) {
x = false; // stores 0 in x
} else {
if (fred(1) == 0) {
x = false; // stores 0 in x
} else {
x = true; // stores 1 in x
}
}
Clearly, the boolean && is a lot more concise way to express this surprisingly complex piece
of logic.
As with bitwise AND and boolean AND, there are differences between bitwise OR and
boolean OR. The bitwise OR operator | always evaluates both of its operands, whereas the
boolean OR operator || evaluates its right operand only if its left operand is false (zero). Also,
bitwise | operates independently on all of the bits in its operands, whereas boolean || treats
both of its operands as either true (nonzero) or false (zero), and evaluates to either true (if
either operand is nonzero) or false (if both operands are zero).
COPY
Explain
void setup()
{
int pin;
for (pin=2; pin <= 13; ++pin) {
pinMode (pin, OUTPUT);
}
for (pin=2; pin <= 10; ++pin) {
digitalWrite (pin, LOW);
}
for (pin=11; pin <= 13; ++pin) {
digitalWrite (pin, HIGH);
}
}
It turns out there is a way to accomplish the same thing using direct access to Atmega8
hardware ports and bitwise operators:
COPY
Explain
void setup()
{
// set pins 1 (serial transmit) and 2..7 as output,
// but leave pin 0 (serial receive) as input
// (otherwise serial port will stop working!) ...
DDRD = B11111110; // digital pins 7,6,5,4,3,2,1,0
COPY
// set pins 8..13 as output...
DDRB = B00111111; // digital pins -,-,13,12,11,10,9,8
COPY
// Turn off digital output pins 2..7 ...
PORTD &= B00000011; // turns off 2..7, but leaves pins 0 and 1 alone
COPY
// Write simultaneously to pins 8..13...
PORTB = B00111000; // turns on 13,12,11; turns off 10,9,8
}
This code takes advantage of the fact that the control registers DDRD and DDRB each
contain 8 bits that determine whether a given digital pin is output (1) or input (0). The upper
2 bits in DDRB are not used, because there is no such thing is digital pin 14 or 15 on the
Atmega8. Likewise, the port registers PORTB and PORTD contain one bit for the most
recently written value to each digital pin, HIGH (1) or LOW (0).
Generally speaking, doing this sort of thing is not a good idea. Why not? Here are a few
reasons:
The code is much more difficult for you to debug and maintain and is a lot harder for other
people to understand. It only takes a few microseconds for the processor to execute code, but
it might take hours for you to figure out why it isn't working right and fix it! Your time is
valuable, right? But the computer's time is very cheap, measured in the cost of the electricity
you feed it. Usually, it is much better to write code the most obvious way.
The code is less portable. If you use digitalRead() and digitalWrite(), it is much easier to
write code that will run on all of the Atmel microcontrollers, whereas the control and port
registers can be different on each kind of microcontroller.
It is a lot easier to cause unintentional malfunctions with direct port access. Notice how the
line DDRD = B11111110; above mentions that it must leave pin 0 as an input pin. Pin 0 is
the receive line on the serial port. It would be very easy to accidentally cause your serial port
to stop working by changing pin 0 into an output pin! Now that would be very confusing
when you suddenly are unable to receive serial data, wouldn't it?
So you might be saying to yourself, great, why would I ever want to use this stuff then? Here
are some of the positive aspects of direct port access:
If you are running low on program memory, you can use these tricks to make your code
smaller. It requires a lot fewer bytes of compiled code to simultaneously write a bunch of
hardware pins simultaneously via the port registers than it would using a for loop to set each
pin separately. In some cases, this might make the difference between your program fitting in
flash memory or not!
Sometimes you might need to set multiple output pins at exactly the same time. Calling
digitalWrite(10,HIGH); followed by digitalWrite(11,HIGH); will cause pin 10 to go HIGH
several microseconds before pin 11, which may confuse certain time-sensitive external digital
circuits you have hooked up. Alternatively, you could set both pins high at exactly the same
moment in time using PORTB |= B1100;
You may need to be able to turn pins on and off very quickly, meaning within fractions of a
microsecond. If you look at the source code in lib/targets/arduino/wiring.c, you will see that
digitalRead() and digitalWrite() are each about a dozen or so lines of code, which get
compiled into quite a few machine instructions. Each machine instruction requires one clock
cycle at 16MHz, which can add up in time-sensitive applications. Direct port access can do
the same job in a lot fewer clock cycles.
More advanced example: disabling an interrupt
Now let's take what we have learned and start to make sense of some of the weird things you
will sometimes see advanced programmers do in their code. For example, what does it mean
when someone does the following?
COPY
// Disable the interrupt.
GICR &= ~(1 << INT0);
This is an actual code sample from the Arduino 0007 runtime library, in the file lib\targets\
arduino\winterrupts.c. First of all, we need to know what GICR and INT0 mean. It turns out
that GICR is a control register that defines whether certain CPU interrupts are enabled (1) or
disabled (0). If we search through the Arduino standard header files for INT0, we find
various definitions. Depending on what kind of microcontroller you are writing for, you have
either
COPY
#define INT0 6
or
COPY
#define INT0 0
So on some processors, the above line of code will compile to:
COPY
GICR &= ~(1 << 0);
and on others, it will compile to:
COPY
GICR &= ~(1 << 6);
Let us study the latter case, as it is more illustrative. First of all, the value (1 << 6) means that
we shift 1 left by 6 bits, which is the same as 26, or 64. More useful in this context is to see
this value in binary: 01000000. Then, the bitwise NOT operator ~ is applied to this value,
resulting in all the bits being toggled: 10111111. Then the bitwise AND assignment operator
is used, so the code above has the same effect as:
COPY
GICR = GICR & B10111111;
This has the effect of leaving all the bits alone in GICR, except for the second-to-highest bit,
which is turned off.
In the case where INT0 has been defined to 0 for your particular microcontroller, the line of
code would instead be interpreted as:
COPY
GICR = GICR & B11111110;
which turns off the lowest bit in the GICR register but leaves the other bits as they were. This
is an example of how the Arduino environment can support a wide variety of
microcontrollers with a single line of runtime library source code.
A simple way to store such an image is using an array of integers. The code for this approach
might look like this:
COPY
Explain
const prog_uint8_t BitMap[5][7] = { // store in program memory to save RAM
{1,1,0,0,0,1,1},
{0,0,1,0,1,0,0},
{0,0,0,1,0,0,0},
{0,0,1,0,1,0,0},
{1,1,0,0,0,1,1}
};
COPY
Explain
void DisplayBitMap()
{
for (byte x=0; x<5; ++x) {
for (byte y=0; y<7; ++y) {
byte data = pgm_read_byte (&BitMap[x][y]); // fetch data from program memory
if (data) {
// turn on the LED at location (x,y)
} else {
// turn off the LED at location (x,y)
}
}
}
}
If this were the only bitmap you had in your program, this would be a simple and effective
solution to the problem. We are using 1 byte of program memory (of which there are about
7K available in the Atmega8) for each pixel in our bitmap, for a total of 35 bytes. This is not
so bad, but what if you wanted a bitmap for each of the 96 printable characters in the ASCII
character set? This would consume 96*35 = 3360 bytes, which would leave a lot less flash
memory for holding your program code.
There is a much more efficient way to store a bitmap. Let us replace the 2-dimensional array
above with a 1-dimensional array of bytes. Each byte contains 8 bits, and we will use the
lowest 7 bits of each to represent the 7 pixels in a column of our 5x7 bitmap:
COPY
Explain
const prog_uint8_t BitMap[5] = { // store in program memory to save RAM
B1100011,
B0010100,
B0001000,
B0010100,
B1100011
};
(Here we are using the predefined binary constants available starting in Arduino 0007.) This
allows us to use 5 bytes for each bitmap instead of 35. But how do we make use of this more
compact data format? Here is the answer: we rewrite the function DisplayBitMap() to access
the individual bits in each byte of the BitMap...
COPY
Explain
void DisplayBitMap()
{
for (byte x=0; x<5; ++x) {
byte data = pgm_read_byte (&BitMap[x]); // fetch data from program memory
for (byte y=0; y<7; ++y) {
if (data & (1<<y)) {
// turn on the LED at location (x,y)
} else {
// turn off the LED at location (x,y)
}
}
}
}
The crucial line to understand is
COPY
if (data & (1<<y)) {
The expression (1<<y) selects a given bit inside data that we want to access. Then using
bitwise AND, data & (1<<y) tests the given bit. If that bit is set, a nonzero value results,
causing the if to see it as being true. Otherwise, if the bit is zero, it is treated as false, so the
else executes.
Quick Reference
In this quick reference, we refer to the bits in a 16-bit integer starting with the least
significant bit as bit 0, and the most significant bit (the sign bit if the integer is signed) as bit
15, as illustrated in this diagram:
bit integer
Whenever you see the variable n, its value is assumed to be 0 through 15.
COPY
y = (x >> n) & 1; // n=0..15. stores nth bit of x in y. y becomes 0 or 1.
COPY
x &= ~(1 << n); // forces nth bit of x to be 0. all other bits left alone.
COPY
x &= (1<<(n+1))-1; // leaves alone the lowest n bits of x; all higher bits set to 0.
COPY
x |= (1 << n); // forces nth bit of x to be 1. all other bits left alone.
COPY
x ^= (1 << n); // toggles nth bit of x. all other bits left alone.
COPY
x = ~x; // toggles ALL the bits in x.
Here is an interesting function that uses both bitwise & and boolean &&. It returns true if and
only if the given 32-bit integer x is a perfect power of 2, i.e., 1, 2, 4, 8, 16, 32, 64, etc. For
example, calling IsPowerOfTwo(64) will return true, but IsPowerOfTwo(65) returns false. To
see how this function works, let us use the number 64 as an example of a power of 2. In
binary, 64 is 1000000. When we subtract 1 from 1000000, we get 0111111. Applying bitwise
&, the result is 0000000. But if we do the same with 65 (binary 1000001), we get 1000001 &
1000000 == 1000000, which is not zero.
COPY
bool IsPowerOfTwo (long x)
{
return (x > 0) && (x & (x-1) == 0);
}
Here is a function that counts how many bits in the 16-bit integer x are 1 and returns the
count:
COPY
Explain
int CountSetBits (int x)
{
int count = 0;
for (int n=0; n<16; ++n) {
if (x & (1<<n)) {
++count;
}
}
return count;
}
Another way is this:
COPY
Explain
int CountSetBits (int x)
{
unsigned int count;
for (count = 0; x; count++)
x &= x - 1;
return count;
}
Various tricks for common bit-oriented operations can be found here.
Multimeter Basics
Learn about different multimeter features, how they function, and how to use this essential
tool.
AuthorJosé Garcia
Last revision08/02/2024
A multimeter is a test tool, mostly used in electronics, that should always be present in the
FabLab of a Maker.
It is a diagnostic tool that allows us, for instance to:
debug circuits,
display the value of resistors,
measure voltage and current in our circuits,
identify conductive/non conductive materials, and much more!
Parts of the Multimeter
There are many different types of multimeters, some with more or less features. The
following image lists the parts that the majority of multimeters have.
Overview of a multimeter
Overview of a multimeter
The display will show various numeric values of the different measurements we take with the
multimeter.
By changing the position of the measurement selector switch, we will be able to configure the
multimeter to measure different parameters.
Depending on the parameters we want to measure, the connection of the terminals should be
done in an appropriate manner.
How to choose a multimeter
It is hard to say exactly which characteristics a multimeter should have, since there are
different needs depending on the background of the user and the intended usage, but let's say
that independently of the level of the user, a multimeter should have:
Voltage reading
Current reading
Resistance reading, from at least 10 Ohm to 1M Ohm
Continuity detection (with acoustic feedback)
Then, depending on the user background, there will be some features that could make the
multimeter easier to use, or make it a more complex tool. Some of these features are:
Autoscale: This feature will configure the data scale of the multimeter to show the readings in
the easiest format possible for the user to understand them. However, this feature may
compromise the accuracy of the readings.
Hold: This feature "freezes" the reading on the screen, so the user can check it out even after
removing the terminal connectors from the measurement points.
Advanced features such as: Frequency counter, capacitance testing, inductance testing, duty
cycle, etc. These features allow us to measure quite advanced properties of different
components and signals in the electronic circuits; but usually a background in electronics is
needed, to makes sense of these readings and to know how to execute the measurements
correctly in the circuits.
How to use a Multimeter
Multimeters have different working modes, depending on what are you interested on
measuring. Let's start by how to use a multimeter to measure voltage, resistance, conductivity
and current.
Reading voltage
This will require to place the measurement selector in the Volts section the black terminal in
the COM and the red terminal in the V|Ω|mA terminal.
With this configuration, the selected scale should be adjusted based on the measured voltage.
If you want to measure the voltage close to a 9V battery, the scale should be 20.
The scale limits the higher value that can be measured, meaning that if the selector is within
the 2000m, the highest value that can be measured with that scale is 2000mV = 2V.
In order to know what scale to select, you should check what is the approximate value that
you will be measuring, and select the scale according to it:
Terminals
Once you have selected the scale to use, it is time to use the terminals:
The black terminal (Common) should be placed in the point of the circuit closer to the ground
"-" terminal of the battery.
The red terminal should be placed in the point you want to know what is the voltage in the
circuit.
The image below shows how to read the voltage falling in an LED. In order to know the
voltage falling on a component, you will need to connect the multimeter in parallel with it.
You can see how the black terminal checks the negative terminal of the LED, which is
connected to the negative pole of the battery and the red terminal on the positive terminal of
the LED.
Reading resistance
In order to measure resistance, the measurement selector should be in the Ohm section.
The scale to measure resistance works exactly as explained in the "Measuring voltage
section".
In order to know the value of a resistor, you need to keep in mind that the measurement must
be done on a resistor that is not connected to any other component! Then, you can make the
reading:
Place each one of the terminals of the multimeter in each one of the terminals of the resistor.
Reading resistance
Reading resistance
Continuity Testing
This feature allows you to know if a material is or is not conductive.
To make this test, you will need to "touch" a material with both terminals of the multimeter,
once you do so, depending on the multimeter you will see:
Once the connections are configured, as shown above, and the selector is properly placed you
can measure the current that is flowing on a circuit. Just make sure that the multimeter is
connected in series with the rest of the components of that same circuit.
The image below shows how to read the current flowing through a circuit with a LED and a
resistor. Here, you can see how the multimeter is connected in series with the resistor and the
LED, as if it was one more component of the circuit.
Reading current
AuthorJosé Bagur
Last revision08/02/2024
Usually, when we work on building electronic circuits projects, we use breadboards,
especially when prototyping. When we have finished the prototyping stage, we often need to
migrate our projects from a breadboard to a dedicated board, like printed circuit boards
(PCBs), to have our project safely and for a long time. It is in these cases when we need to
know how to solder electronic parts or components.
Soldering is a process where two electronic parts or components are joined together by
melting solder around an electrical or/and mechanical connection between those components
using a handheld tool called soldering iron. Solder creates a solid and permanent electrical
and mechanical bond between the electronic components after it cools.
For mastering soldering, we need practice, a lot, but also good tools. Let's first talk about the
essential tools and materials we need to start soldering.
Soldering Tools
Soldering Irons and Soldering Stations
A soldering iron is the fundamental piece of handheld equipment used in the process of
soldering. Soldering irons heat up to melt solder around electrical or mechanical connections;
as it melts, solder flows into the spaces between and around two components. Once bonded,
the solder is left to cool and harden, creating a permanent and conductive join; it can melt
back into liquid form by reheating enough the join. The process of reheating and separating a
previously soldered joint is called desoldering.
A soldering station is a more advanced and professional type of soldering iron with a separate
temperature control unit; soldering irons provide just one fixed heat setting while soldering
stations offer a range of specific temperatures. Soldering stations provide a far greater degree
of accuracy for heat-critical applications.
15W to 30W pen-style soldering irons are a good start for beginners.
Conical tip: a fine, pencil-like tip that can deliver heat to smaller areas without affecting its
surroundings. This tip is a standard-shaped bit for general applications, but it is also handy
for delicate and precision electronics.
Chisel tip: a broad and flattened tip well-suited for soldering wires, through-hole components,
surface-mount components, and desoldering.
Conical (left) and chisel (right) soldering tips.
Conical (left) and chisel (right) soldering tips.
Finding the best tip for soldering jobs can be tricky. Conical and chisel tips should be enough
for soldering standard through-hole and surface-mount electronic components for starters.
One of the most significant causes of soldering problems is poor tip maintenance. It is
essential to take good care of your soldering tips to increase their overall longevity and
reduce the need for regular replacement.
A better alternative are brass sponges for cleaning soldering tips. Brass sponges removes
debris in tips better; they have a smaller thermal shock and they don't need water as
conventional sponges.
Solder
Solder is a metal alloy that melts to create a permanent bond between electrical parts or
components. Solder comes in both lead and lead-free presentations, also in different
diameters; the most common diameters used are 0.8mm and 1.5mm. Inside the solder core a
material known as flux helps improve electrical contact and mechanical strength of solder.
When using solder, make sure to have proper ventilation and to wash your hands after
soldering.
Helping Hands
A helping hand, also called "third hand," is a device that can assist you while soldering by
holding the parts or components you are trying to solder, leaving your hands free to work.
Soldering 101
Now that we know about the essential tools and materials used for soldering, let's get into
soldering. Before we can start, we need to prepare our soldering iron tip by tinning it with
solder, this should be always the first step. Tinning is a process that helps to improve heat
transfer from the soldering iron to the electronic part or component we want to solder; it also
helps to protect the soldering tip against oxidation.
Let's begin by making sure our soldering tip is attached correctly to our soldering iron, then
turn it on and let it heat up. If you use a soldering station, set the temperature to 400° C (or
752° F). When the soldering iron, or soldering station, is heated up and ready:
Wipe the soldering tip with a conventional wet sponge or a brass sponge. The purpose of this
is to clean the tip by removing any present oxidation or debris on it. After cleaning the
soldering tip, we should wait a few seconds to let the soldering tip heat up again.
Touch the solder with the soldering iron tip, apply a small amount of solder on the soldering
tip making sure the solder flows evenly around it.
Remember to tinning the soldering iron tip before and after every soldering session. Tinning
is a good practice that can increase soldering tip life.
Then, flip the circuit board and bend its leads outwards 45°.
Touch the copper pad and one of the resistors leads simultaneously, hold the soldering iron in
place to heat the joint formed by the pad and the resistor lead for three to four seconds.
When there is enough solder in the joint, remove the soldering iron and let it cool down
naturally. Once cool, snip the extra wire from the resistor lead. Avoid bad joints by blowing
on the solder and not allowing it to cool down naturally. Repeat this process with the other
resistor lead and the rest of the components of the circuit.
Good solder joints are smooth, shiny, and have a volcano-like shape. Good solder joints also
have enough solder to cover the entire joint but not too much to spill it.
Soldering Wires
Working with electric circuits also involves working with wires; let's learn how to solder
them. For soldering wires, it's also recommended to use a helping hand or another type of
clamp device:
Let's start by removing the insulation from the end of the wires we are going to solder. If the
wire is stranded, we must twist the strands with our fingers.
Touch the end of one wire with the soldering tip, hold the soldering iron in place to heat the
wire for three to four seconds.
While holding the soldering iron on the wire, apply the solder until it's fully coated. Repeat
this process on the other wire end.
With the two wires tinned, hold their tinned end on top of each other and heat them with the
soldering iron. This should melt the solder and coat them. If it is needed, apply solder so both
wires are coated evenly.
Remove the soldering iron and let it cool down and harden naturally.
Use heat shrink to cover connections between wires.
Desoldering
What happens if we want to remove a solder joint? Or if we're going to change it to correct?
Or what about removing a component from a circuit board. Solder joints can be removed or
changed easily by reheating and separating them; this is named desoldering. To desolder a
joint, we will need desoldering braid, also known as solder wick.
Place the desoldering braid on the top of the solder joint to remove.
Heat the soldering braid and the solder joint with the tip of a soldering iron. Start removing
the soldering braid; you will see that solder has been extracted and removed.
Be careful while using a soldering braid because it will get hot.
If you have a lot of solder, using desoldering braid may not be the best option. In this case, a
device called solder sucker is a better option. A solder sucker is a handheld mechanical
vacuum that sucks up hot solder, usually by pressing a button.
Another example is the MKR SD Proto Shield. This shield allows you to easily connect a
microSD card to an MKR family board; in this shield, there's also a small prototyping area
for soldering components.
Check out the documentation of the MKR SD Proto Shield for more information, tutorials
and resources.
Last revision08/02/2024
This article was revised on 2021/11/18 by Karl Söderby.
The LiquidCrystal library allows you to control LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you can usually tell them by
the 16-pin interface.
The LCDs have a parallel interface, meaning that the microcontroller has to manipulate
several interface pins at once to control the display. The interface consists of the following
pins:
A register select (RS) pin that controls where in the LCD's memory you're writing data to.
You can select either the data register, which holds what goes on the screen, or an instruction
register, which is where the LCD's controller looks for instructions on what to do next.
A Read/Write (R/W) pin that selects reading mode or writing mode
An Enable pin that enables writing to the registers
8 data pins (D0 -D7). The states of these pins (high or low) are the bits that you're writing to a
register when you write, or the values you're reading when you read.
There's also a display contrast pin (Vo), power supply pins (+5V and GND) and LED
Backlight (Bklt+ and BKlt-) pins that you can use to power the LCD, control the display
contrast, and turn on and off the LED backlight, respectively.
The process of controlling the display involves putting the data that form the image of what
you want to display into the data registers, then putting instructions in the instruction register.
The LiquidCrystal Library simplifies this for you so you don't need to know the low-level
instructions.
The Hitachi-compatible LCDs can be controlled in two modes: 4-bit or 8-bit. The 4-bit mode
requires seven I/O pins from the Arduino, while the 8-bit mode requires 11 pins. For
displaying text on the screen, you can do most everything in 4-bit mode, so example shows
how to control a 16x2 LCD in 4-bit mode.
Hardware Required
Arduino Board
LCD Screen (compatible with Hitachi HD44780 driver)
pin headers to solder to the LCD display pins
10k ohm potentiometer
220 ohm resistor
hook-up wires
breadboard
Circuit
Note that this circuit was originally designed for the Arduino UNO. As the Arduino is
communicating with the display using SPI, pin 11 & 12 will change depending on what board
you are using. For example, on a MKR WiFi 1010, the SPI bus is attached to pin 8 & 11.
Before wiring the LCD screen to your Arduino board we suggest to solder a pin header strip
to the 14 (or 16) pin count connector of the LCD screen, as you can see in the image further
up.
To wire your LCD screen to your board, connect the following pins:
Schematic
The schematic (made using Fritzing).
The schematic (made using Fritzing).
COPY
Explain
/*
LiquidCrystal Library - Hello World
The circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* LCD VSS pin to ground
* LCD VCC pin to 5V
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
https://docs.arduino.cc/learn/electronics/lcd-displays
*/
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("hello, world!");
}
void loop() {
// set the cursor to column 0, line 1
// (note: line 1 is the second row, since counting begins with 0):
lcd.setCursor(0, 1);
// print the number of seconds since reset:
lcd.print(millis() / 1000);
}
Autoscroll Example
This example sketch shows how to use the autoscroll() and noAutoscroll() methods to move
all the text on the display left or right.
autoscroll() moves all the text one space to the left each time a letter is added
noAutoscroll() turns scrolling off
This sketch prints the characters 0 to 9 with autoscroll off, then moves the cursor to the
bottom right, turns autoscroll on, and prints them again.
COPY
Explain
/*
library works with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
The circuit:
* 10K resistor:
by David A. Mellis
library modified 5 Jul 2009
by Tom Igoe
by Tom Igoe
by Arturo Guadalupi
http://www.arduino.cc/en/Tutorial/LiquidCrystalAutoscroll
*/
void setup() {
lcd.begin(16, 2);
}
void loop() {
lcd.setCursor(0, 0);
// print from 0 to 9:
lcd.print(thisChar);
delay(500);
}
// set the cursor to (16,1):
lcd.setCursor(16, 1);
lcd.autoscroll();
// print from 0 to 9:
lcd.print(thisChar);
delay(500);
lcd.noAutoscroll();
lcd.clear();
}
Blink Example
This example sketch shows how to use the blink() and noBlink() methods to blink a block-
style cursor.
COPY
Explain
/*
library works with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
This sketch prints "Hello World!" to the LCD and makes the
The circuit:
* 10K resistor:
by David A. Mellis
by Tom Igoe
by Tom Igoe
by Arturo Guadalupi
http://www.arduino.cc/en/Tutorial/LiquidCrystalBlink
*/
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
lcd.print("hello, world!");
}
void loop() {
lcd.noBlink();
delay(3000);
lcd.blink();
delay(3000);
}
Cursor
This example sketch shows how to use the cursor() and noCursor() methods to control an
underscore-style cursor.
COPY
Explain
/*
library works with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
The circuit:
* 10K resistor:
by David A. Mellis
by Tom Igoe
by Tom Igoe
by Arturo Guadalupi
http://www.arduino.cc/en/Tutorial/LiquidCrystalCursor
*/
void setup() {
lcd.print("hello, world!");
}
void loop() {
lcd.noCursor();
delay(500);
lcd.cursor();
delay(500);
}
Display Example
This example sketch shows how to use the display() and noDisplay() methods to turn on and
off the display. The text to be displayed will still be preserved when you use noDisplay() so
it's a quick way to blank the display without losing everything on it.
COPY
Explain
/*
LiquidCrystal Library - display() and noDisplay()
This sketch prints "Hello World!" to the LCD and uses the
display() and noDisplay() functions to turn on and off
the display.
The circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
http://www.arduino.cc/en/Tutorial/LiquidCrystalDisplay
*/
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("hello, world!");
}
void loop() {
// Turn off the display:
lcd.noDisplay();
delay(500);
// Turn on the display:
lcd.display();
delay(500);
}
Scroll Example
This example sketch shows how to use the scrollDisplayLeft() and scrollDisplayRight()
methods to reverse the direction the text is flowing. It prints "Hello World!", scrolls it
offscreen to the left, then offscreen to the right, then back to home.
COPY
Explain
/*
LiquidCrystal Library - scrollDisplayLeft() and scrollDisplayRight()
This sketch prints "Hello World!" to the LCD and uses the
scrollDisplayLeft() and scrollDisplayRight() methods to scroll
the text.
The circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
http://www.arduino.cc/en/Tutorial/LiquidCrystalScroll
*/
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("hello, world!");
delay(1000);
}
void loop() {
// scroll 13 positions (string length) to the left
// to move it offscreen left:
for (int positionCounter = 0; positionCounter < 13; positionCounter++) {
// scroll one position left:
lcd.scrollDisplayLeft();
// wait a bit:
delay(150);
}
}
Serial to Display Example
This example sketch accepts serial input from a host computer and displays it on the LCD. To
use it, upload the sketch, then open the Serial Monitor and type some characters and click
Send. The text will appear on your LCD.
COPY
Explain
/*
LiquidCrystal Library - Serial Input
The circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
Library originally added 18 Apr 2008
by David A. Mellis
library modified 5 Jul 2009
by Limor Fried (http://www.ladyada.net)
example added 9 Jul 2009
by Tom Igoe
modified 22 Nov 2010
by Tom Igoe
modified 7 Nov 2016
by Arturo Guadalupi
http://www.arduino.cc/en/Tutorial/LiquidCrystalSerialDisplay
*/
void setup() {
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// initialize the serial communications:
Serial.begin(9600);
}
void loop() {
// when characters arrive over the serial port...
if (Serial.available()) {
// wait a bit for the entire message to arrive
delay(100);
// clear the screen
lcd.clear();
// read all the available characters
while (Serial.available() > 0) {
// display each character to the LCD
lcd.write(Serial.read());
}
}
}
Set Cursor Example
This example sketch shows how to use the setCursor() method to reposition the cursor. To
move the cursor, just call setCursor() with a row and column position. For example, for a
2x16 display:
COPY
lcd.setCursor(0, 0); // top left
lcd.setCursor(15, 0); // top right
lcd.setCursor(0, 1); // bottom left
lcd.setCursor(15, 1); // bottom right
Here is the full example:
COPY
Explain
/*
library works with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
This sketch prints to all the positions of the LCD using the
setCursor() method:
The circuit:
* 10K resistor:
by David A. Mellis
by Tom Igoe
by Arturo Guadalupi
http://www.arduino.cc/en/Tutorial/LiquidCrystalSetCursor
*/
// these constants won't change. But you can change the size of
// your LCD using them:
void setup() {
lcd.begin(numCols, numRows);
}
void loop() {
lcd.write(thisLetter);
delay(200);
}
}
Text Direction Example
This example sketch shows how to use the leftToRight() and rightToLeft() methods. These
methods control which way text flows from the cursor.
rightToLeft() causes text to flow to the left from the cursor, as if the display is right-justified.
leftToRight() causes text to flow to the right from the cursor, as if the display is left-justified.
This sketch prints a through l right to left, then m through r left to right, then s through z right
to left again.
COPY
Explain
/*
library works with all LCD displays that are compatible with the
Hitachi HD44780 driver. There are many of them out there, and you
The circuit:
* 10K resistor:
by David A. Mellis
by Tom Igoe
by Tom Igoe
by Arturo Guadalupi
http://www.arduino.cc/en/Tutorial/LiquidCrystalTextDirection
*/
void setup() {
lcd.begin(16, 2);
void loop() {
if (thisChar == 'm') {
lcd.rightToLeft();
if (thisChar == 's') {
lcd.leftToRight();
// reset at 'z':
// go to (0,0):
lcd.home();
// start again at 0
thisChar = 'a';
lcd.write(thisChar);
// wait a second:
delay(1000);
thisChar++;
}
Custom Character
This example demonstrates how to add custom characters on an LCD display.
Note that this example requires an additional potentiometer:
COPY
Explain
/*
LiquidCrystal Library - Custom Characters
This sketch prints "I <heart> Arduino!" and a little dancing man
to the LCD.
The circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* 10K potentiometer:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
* 10K poterntiometer on pin A0
AuthorArduino
Last revision08/02/2024
Stepper motors, due to their unique design, can be controlled to a high degree of accuracy
without any feedback mechanisms. The shaft of a stepper, mounted with a series of magnets,
is controlled by a series of electromagnetic coils that are charged positively and negatively in
a specific sequence, precisely moving it forward or backward in small "steps".
There are two types of steppers, Unipolars and Bipolars, and it is very important to know
which type you are working with. For each of the motors, there is a different circuit. The
example code will control both kinds of motors. See the unipolar and bipolar motor
schematics for information on how to wire up your motor.
The stepper is controlled by with digital pins 8, 9, 10, and 11 for either unipolar or bipolar
motors. The Arduino board will connect to a U2004 Darlington Array if you're using a
unipolar stepper or a SN754410NE H-Bridge if you have a bipolar motor.
Hardware Required
Arduino Board
stepper motor
U2004 Darlington Array (if using a unipolar stepper)
SN754410ne H-Bridge (if using a bipolar stepper)
power supply appropriate for your particular stepper
hook-up wires
breadboard
Circuit
Below you'll find circuits for both unipolar and bipolar steppers. In either case, it is best to
power your stepper motors from an external supply, as they draw too much to be powered
directly from your Arduino board.
Note: Both circuits below are four wire configurations. Two wire configurations will not
work with the code provided.
Examples
MotorKnob
A stepper motor follows the turns of a potentiometer (or other sensor) on analog input 0.
COPY
Explain
#include <Stepper.h>
void setup() {
// set the speed of the motor to 30 RPMs
stepper.setSpeed(30);
}
void loop() {
// get the sensor value
int val = analogRead(0);
COPY
Explain
#include <Stepper.h>
const int stepsPerRevolution = 200; // change this to fit the number of steps per revolution
// for your motor
void setup() {
// set the speed at 60 rpm:
myStepper.setSpeed(60);
// initialize the serial port:
Serial.begin(9600);
}
void loop() {
// step one revolution in one direction:
Serial.println("clockwise");
myStepper.step(stepsPerRevolution);
delay(500);
COPY
Explain
#include <Stepper.h>
const int stepsPerRevolution = 200; // change this to fit the number of steps per revolution
// for your motor
void setup() {
// initialize the serial port:
Serial.begin(9600);
}
void loop() {
// step one step:
myStepper.step(1);
Serial.print("steps:");
Serial.println(stepCount);
stepCount++;
delay(500);
}
StepperSpeedControl
The motor will rotate in a clockwise direction. The higher the potentiometer value, the faster
the motor speed. Because setSpeed() sets the delay between steps, you may notice the motor
is less responsive to changes in the sensor value at low speeds.
COPY
Explain
#include <Stepper.h>
const int stepsPerRevolution = 200; // change this to fit the number of steps per revolution
// for your motor
void setup() {
// nothing to do inside the setup
}
void loop() {
// read the sensor value:
int sensorReading = analogRead(A0);
// map it to a range from 0 to 100:
int motorSpeed = map(sensorReading, 0, 1023, 0, 100);
// set the motor speed:
if (motorSpeed > 0) {
myStepper.setSpeed(motorSpeed);
// step 1/100 of a revolution:
myStepper.step(stepsPerRevolution / 100);
}
}
AuthorArduino
Last revision08/02/2024
The Servo Library is a great library for controlling servo motors. In this article, you will find
two easy examples that can be used by any Arduino board.
The first example controls the position of an RC (hobby) servo motor with your Arduino and
a potentiometer. The second example sweeps the shaft of an RC servo motor back and forth
across 180 degrees.
You can also visit the Servo GitHub repository to learn more about this library.
Hardware Required
Arduino Board
Servo Motor
10k ohm potentiometer
hook-up wires
capacitors
power supply
Powering Servo Motors
Servo motors have different power requirements depending on their size and the workload
they are experiencing. A common servo motor such as the Feetech Mini Servo Motor
requires between 4.8 - 6 V at 5 – 6 mA when idle. It doesn't take very much energy to stand
still.
But as soon as the motor starts moving, it starts using more energy, and it gets that energy by
pulling more current from the power source.
If it experiences heavier loads such as added weight or an object blocking its movement , it
naturally needs to use even more energy to move the obstacle, and as a result the current
consumption increases. The current consumption of the motor linked above can reach up to
800 mA.
This high current-draw is generally not safe to draw from an Arduino board. To avoid
damaging our board we need to power the servo motor through an external power supply.
Choosing the correct power supply depends on the servo motor you are using, so always
check the specifications. Pay especially close attention to the:
Note that USB wall chargers are limited to 500 mA (USB 2.0) or 900 mA (USB 3.0).
If your project needs to move around freely without being attached to a power outlet you can
also choose batteries to power the servo. If you need 5 V exactly you can use two 18650 Li-
Ion batteries together with a step-down converter.
A step-down converter is needed because 18650 Li-Ion batteries will give you around 7.4 V.
The max current depends on the specific battery but most of them are designed to output
above 1A which is enough to power our small servo.
If you are using bigger or more servos make sure to check your power requirements
accordingly.
Capacitors are recommended for powering servo motors. They help stabilize the power
supply, minimize voltage drops, and reduce electrical noise. The specific capacitor values
may vary based on the servo motor's requirements, but including them is good practice for
better performance and reliability.
When using a Feetech Mini Servo Motor we recommend using a 100 µF capacitor.
Capacitor
Capacitor
Because some capacitors are polarised (meaning that they have a direction), you may need to
be careful with how you connect them to your circuit. Make sure to connect them correctly
by checking for markings such as a white stripe, a '+' symbol, or a longer lead. If your
capacitor has these, match the indicators of the capacitor with your circuit (pay attention to
the + and - signs), and be careful not to exceed the voltage limits. This precaution helps
prevent issues like leaks or damage that could harm your circuit.
Circuit
Servo motors have three wires: power, ground, and signal. The power wire is typically red,
and should be connected to positive pole (+) of your power source. The ground wire is
typically black or brown and should be connected to the negative pole (-) of your power
source.
The signal pin is typically yellow or orange and should be connected to PWM pin on the
board. In these examples, it is pin number 9.
Knob Circuit
For the Knob example, wire the potentiometer so that its two outer pins are connected to
power (+5V) and ground, and its middle pin is connected to A0 on the board. Then, connect
the servo motor as shown in the circuit below.
Sweep Circuit
For the Sweep example, connect the servo motor as shown in the circuit below.
Examples
Knob
Controlling a servo position using a potentiometer (variable resistor).
COPY
Explain
#include <Servo.h>
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
void loop() {
val = analogRead(potpin); // reads the value of the potentiometer (value between 0
and 1023)
val = map(val, 0, 1023, 0, 180); // scale it to use it with the servo (value between 0 and
180)
myservo.write(val); // sets the servo position according to the scaled value
delay(15); // waits for the servo to get there
}
Sweep
Sweeps the shaft of a RC servo motor back and forth across 180 degrees.
COPY
Explain
#include <Servo.h>
void setup() {
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}
void loop() {
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
}
To the left, you will see three grey boxes. Each one of these represents a voltage input. In the
case of the Arduino Portenta H7, we can either provide power via the USB-C® cable, the
VIN pin on the high density connector or a battery.
You can see more information about the precise schematic location and pins in the schematic
diagram and pinout graphics respectively. The Power Tree is there to provide a visual aid to
better understanding the product and some intermediate components may not be displayed. If
in doubt, refer to the schematics or contact us.
We can also see that the USB-C® and the VIN are connected together. Under each grey box,
we can see that the voltage is listed as 5V. This is the nominal value (see the datasheet for the
allowed voltage ranges). In general, you can expect there to be no difference in the operation
between each approach.
A bit further down, we can see another grey component titled VBATT. In this case, we see
two distinct features: first of all, the arrows are bi-directional. Secondly, the voltage (in the
turquoise box underneath it) is lower. While this can be difficult to understand from a text
based table, having this information can be beneficial to the user when trying to understand
the product from a Model-Based Design approach.
For more information about Model-Based Design, check out the Engineering Kit Rev 2 for
hands on projects.
The current specification noted in the diagram tells you about the maximum amount of
current that can be drawn from that specific voltage source. Consequently you can attach any
sensor or actuator, so long as the maximum current draw is not exceeded. A device always
only draws as much current as it needs to operate. However, the case for voltage is different.
As a general rule, the voltage values must match with a low tolerance and it is not possible to
power a 5V sensor via a 3.3V pin or vice versa. Voltages have to match, while the currents
don't have to match (just be lower). That said, connecting a data line from e.g. a 3.3V sensor
to a 5V micro controller input pin may work depending on the defined TTL levels as long as
the communication is unidirectional. If 5V are applied from the 5V micro controller to the
3.3V peripheral, it may destroy it.
In some cases, components can function over a wide voltage range. Consult the datasheet of
the component to find the supported range.
Next, let us take a look at the block representing the MC34PF1550A0EP component.
The input of the power component, as stated previously, can come from either the 5V power
source or the battery. The voltage and net of these two (as seen in the schematic view) is
written on the turquoise box on the connecting lines. Recall that since the USB-C® and VIN
voltage inputs are connected (as denoted by the black dot connecting the lines together). The
5V voltage is fed to a LDO that has a maximum current capacity of 2A, producing a 4.5V
rail. LDO stands for a Low Dropout linear regulator. These are a type of linear regulator that
are designed to work when the input voltage is slightly above the output voltage. The output
of this LDO is a voltage rail (shown again in a turquoise box) with a voltage of 4.5V. Given
that this is slightly lower than 5V input, it is important that the input voltage is stable since, if
it drops below 4.7V the stability of the Portenta board may be compromised.
Since all other system voltages go through the noted LDO, the maximum current that can
pass through the PMIC is at most 2A. Losses in subsequent voltage conversion will reduce
the real current available to the user. For further details, please refer to the
MC34PF1550A0EP datasheet.
The generated 4.5V is then available for use by DC-DC converters. Here, DC-DC conversion
refers to switching regulators. Depending on their architecture, switching regulators can
increase (boost) or reduce (buck) the voltage levels. A major benefit compared to linear
regulators is their lower PCB footprint and power consumption. However, they can also be
susceptible to noise.
All official Arduino boards have been designed to reduce the noise level to the minimum,
with the use of premium PCB manufacturing and stringent quality control. Purchasing
counterfeit boards may lead to reduced power quality and loss of functionality.
To the right, we can see components that make use of these generated voltages. Note that in
some cases, components may make use of multiple voltage levels to operate.
Graphical styles
As you may have noticed, each type of component has its own visual style. The input blocks
are grey, with a turquoise label underneath showing the nominal voltage. Interconnecting
power rails are shown with a black dot. The power lines themselves are marked by a
turquoise box with the voltage/net. The power conversion blocks are also grey. Under that,
one or more power conversion sub-components are shown. The conversion type
(LDO/CHRG/DCDC) is in yellow, while the maximum current output is in red. These sub-
components are all right aligned. To the bottom left, a Legend is shown.
Further Resources
Various IC manufacturers have material for understanding power conversion, including
switch mode power supplies. These can help advanced users better understand power
conversion that you see in the Power Trees included in the Arduino datasheets. See ON
Semiconductor's Switch-Mode Power Supply Reference Manual or the Power Topologies
Handbook by Texas Instruments.
In order to explain complex interactions, conceptual diagrams similar to the Power Tree are
widely used by domain experts to quickly and efficiently convey information. For a survey of
how conceptual diagrams are used in various disciplines, see:
Ma’ayan, Dor, et al. ‘How Domain Experts Create Conceptual Diagrams and Implications for
Tool Design’. Proceedings of the 2020 CHI Conference on Human Factors in Computing
Systems, ACM, 2020
Tippett, Christine D. ‘What Recent Research on Diagrams Suggests about Learning with
Rather than Learning from Visual Representations in Science’. International Journal of
Science Education, vol. 38, no. 5, Mar. 2016
The Engineering Kit Rev2 goes into more detail about how Model-based Design can be used
to develop and understand complex systems.
The present guide for achieving low power system are applicable for every Arduino boards.
For example, Arm Cortex-M0 32-bit SAMD21 processor based Arduino boards can take
advantage of low-power features. The Arduino SAMD21 boards with wireless protocol with
LoRaWAN® network capability, with module as Murata CMWX1ZZABZ featured from
MKR WAN 1310, can be combined with low power features to operate for an extensive
period. With advanced techniques, such tools as power source guide and self-discharge rates
design applies to every Arduino boards for designing power efficient systems. You can check
out Arduino Documentation Hardware page to find out about Arduino boards.
The library can be managed by going under Sketch -> Include Library -> Manage Libraries in
Arduino IDE and searching for "Arduino Low Power" choosing the latest version.
For when board components needs to be updated, it can be managed by going under Tools ->
Board -> Boards Manager and searching for respective Arduino board family.
You can download the different Arduino IDE version through the link below:
To learn more about the Arduino IDE, follow the links below:
Arduino IDE 1
Arduino IDE 2
Low-Power Design Techniques
There are several different options to reduce the power consumption in microcontrollers:
Sleep Mode
External Events
ADC
Stand-by
The Sleep Technique
The best method of enabling low power features is by putting the processor to sleep. Deep
Sleep mode will allow the device to turn off a variety of internal modules to save most power
consumption. The Deep Sleep mode can be set with a timer to wake up after a defined length
of time is achieved. Additionally there is also a Light Sleep mode, which will allow some of
the internal modules to be powered on to keep such required tasks alive. Usually Deep Sleep
mode is applied to save most power consumption, while the Light Sleep mode will help to
keep some settings alive to track state of external modules when it wakes up.
Within these sleep modes, Stand-By and Idle modes provide different levels of configuration
for putting the microcontroller to low power mode. Depending on the devices requirements,
some components or modules may still have to be running when it is in sleep state. Stand-By
mode is one of the lowest power consumption mode for SAMD21 based boards. This will
stop every clock sources of the microcontroller and set the voltage regulators to be in low
power state. Oscillators can be in 3 different states where it stops or run, and run on behalf of
peripheral request. The device will be then in deep sleep while WFI (Wait For Interrupt) is
active. An interrupt or WDT (Watchdog Timer) will be the triggers to wake up the device
from sleep state.
The same goes for Idle mode as Stand-By mode. The Idle mode will still have the peripherals
running while WFI (Wait For Interrupt) is active. However the device will not be in deep
sleep mode, meaning it will have higher power consumption level compared to Stand-By
mode. The clock sources, on other hand, are dependent on software architect whether to leave
it running or turn it off to lower the power consumption. An interrupt or WDT (Watchdog
Timer) will be the triggers to wake up the device from sleep state as same for this present
mode.
Thus whenever it distinguishes the change in the signal, the microprocessor will wake up and
proceed to initialize designed tasks. For example, for sensing the vibration intensity, low or
excessive level or such resources to be quantified as gas components present in the air.
Batteries will be quoted as a power source as it will power up the device. There are different
batteries with different capacities and self-discharge rates which are not flat. Meaning it will
discharge faster at the beginning. This factor is usually influenced by temperature, which
makes it behave differently depending on the environment it is in. So this must be considered
as a guide and take it into account to design efficient Low Power device.
Batteries like CR2032 possess capacity of 210 mAH with discharging rate of 1% per month.
NiMH AAA batteries possess 900 mAH of higher capacity than CR2032 but discharging rate
of 30% per month; while the AA sized version has 2400 mAH of capacity but same
discharging rate. Li-Ion batteries are 4400 mAH of capacity but 10% discharging rate per
month.
It is clear that there are several types of power sources that can used for the device with
divergent characteristics that can used for design factors. Some more specific design cases
may require strict power source chemical composite requirement, but we will use the capacity
and the discharge rate as main ingredient for designing power efficient devices. As chemical
composite requirement is for safety assurance for the device to be able to stay inside strictly
maintained environment.
This Self-Discharing rate equation can assist you into knowing the discharge rate in which
the power source will suffer in an hour. This value will let you know then it is a similar
approximate rate as continuously consumed rate.
Several design factors comes in play when building a power efficient device. Here, the power
sources suffer from these conditions so it must be taken into account to design consideration.
This is to avoid risk, as mentioned before, on having higher consumption than the actual
device consumption.
However, it is also important to know the form factor of the power source as it will define the
size of the device and will limit the selection of power sources if the device requires to be
very small. For this, advanced low power techniques might be able to guide you through the
design considerations to fully take advantage of this low power guide.
COPY
Explain
#include "ArduinoLowPower.h"
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
LowPower.sleep(5000);
}
Here we can also change a line of the code to perform Deep Sleep mode of the device.
COPY
//LowPower.sleep(5000);
LowPower.deepSleep(5000);
This will set the device into Deep Sleep mode when the device is powered on. Having this
simple example code it is possible to see that it is written inside the loop function, but this
low power code line can be written inside the setup function.
Bear in mind that the setup function runs in first place, as it is the sector where it configures
the board appropriately before it executes different tasks. So it is always recommended to
design the software structure and later start writing the actual code, which will be helpful for
achieving power efficient device.
COPY
Explain
#include "ArduinoLowPower.h"
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(pin, mode);
LowPower.attachInterruptWakeup(pin, callback, mode);
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
LowPower.sleep(10000);
}
void callback() {
// This function will be called once on device wakeup
// You can do some little operations here (like changing variables which will be used in the
loop)
// Remember to avoid calling delay() and long running functions since this functions
executes in interrupt context
}
Here requires to define LowPower.attachInterruptWakeup(pin, callback, mode) function with
pin of the device to be handled, the design the task inside the callback, and the mode to define
the transition to sense change on the defined pin. The mode can be defined within 3 different
settings: FALLING, RISING, and CHANGE.
Falling mode means when the signal on the defined pin sense is in negative trend; Rising
mode is when the signal is in positive trend; and Change mode is either trends whenever the
defined pin senses any type of shift. For pinMode(pin, mode), the modes are INPUT,
OUTPUT, or INPUT_PULLUP. A defined external event based low power example can be
seen as follows. This example can be found by navigating to Examples -> Arduino Low
Power -> ExternalWakeup.
COPY
Explain
#include "ArduinoLowPower.h"
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
// Set pin 8 as INPUT_PULLUP to avoid spurious wakeup
pinMode(pin, INPUT_PULLUP);
// Attach a wakeup interrupt on pin 8, calling repetitionsIncrease when the device is woken
up
LowPower.attachInterruptWakeup(pin, repetitionsIncrease, CHANGE);
}
void loop() {
for (int i = 0; i < repetitions; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
// Triggers an infinite sleep (the device will be woken up only by the registered wakeup
sources)
// The power consumption of the chip will drop consistently
LowPower.sleep();
}
void repetitionsIncrease() {
// This function will be called once on device wakeup
// You can do some little operations here (like changing variables which will be used in the
loop)
// Remember to avoid calling delay() and long running functions since this functions
executes in interrupt context
repetitions ++;
}
ADC (Analog to Digital Converter) Based Low Power Example
Hardware Needed: Any SAMD21 Based Arduino Boards (MKR Family)
Modifying a bit of the previous external event based low power example, it is possible to
configure ADC (Analog to Digital Converter) as a wake up source given a defined range of
voltage detection reading. This example can be found by navigating to Examples -> Arduino
Low Power -> AdcWakeup.
COPY
Explain
#include "ArduinoLowPower.h"
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(pin, INPUT);
}
void loop() {
for (int i = 0; i < repetitions; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
// Attach an ADC interrupt on pin A0, calling repetitionsIncrease when the voltage is
outside the given range.
// This should be called immediately before LowPower.sleep() because it reconfigures the
ADC internally.
LowPower.attachAdcInterrupt(pin, repetitionsIncrease, ADC_INT_OUTSIDE, lo, hi);
// Triggers an infinite sleep (the device will be woken up only by the registered wakeup
sources)
// The power consumption of the chip will drop consistently
LowPower.sleep();
// Detach the ADC interrupt. This should be called immediately after LowPower.sleep()
because it restores the ADC configuration after waking up.
LowPower.detachAdcInterrupt();
}
void repetitionsIncrease() {
// This function will be called once on device wakeup
// You can do some little operations here (like changing variables which will be used in the
loop)
// Remember to avoid calling delay() and long running functions since this functions
executes in interrupt context
repetitions ++;
}
Getting in more deeper with the previous code, we will be defining the value window in
which the analog pin A0 of the device will operate.
COPY
uint16_t lo = max(value - margin, 0);
uint16_t hi = min(value + margin, UINT16_MAX);
The ADC (Analog to Digital Converter) interrupt configuration is done inside the loop
function so it is possible to reconfigure immediately before the device goes into sleep state.
COPY
LowPower.attachAdcInterrupt(pin, repetitionsIncrease, ADC_INT_OUTSIDE, lo, hi);
After the device wakes up within configured ADC (Analog to Digital Converter) interrupt, it
will detach the ADC interrupt so it restores its ADC configuration.
COPY
LowPower.detachAdcInterrupt();
Callback functions are to be used when the system wakes up from sleep state via configured
interruption. In this function, and as an entire software architecture, usually it is a good
practice to avoid using delay() and long running functions. This is to avoid what is called
Blocking Operation and to be designed in Non-Blocking Operation fashion, which very
helpful for this types of design cases. In this instances, this will help design power efficient
system in parallel being a responsive system.
This example shows MKR WAN1300/1310 as a remote transmitter device that sends alive
beacon status message periodically. This is to simulate a device broadcasting beacon data
every certain amount of time and requires extensive operation lifetime. The receiver device
will be stationary as reception tower. The remote transmitter device will have the SAMD21
go into sleep state, but also the On-Board Murata module to remove unnecessary power
consumption.
For more information on the LoRa library, please visit the Arduino LoRa repository on
GitHub.
COPY
Explain
// Low Power Library
#include "ArduinoLowPower.h"
// LoRa Library
#include <SPI.h>
#include <LoRa.h>
void setup() {
Serial.begin(9600);
while (!Serial);
// LoRa Setup
Serial.println(F("LoRa Sender"));
if (!LoRa.begin(868E6)) {
Serial.println(F("Starting LoRa failed!"));
while (1);
} else {
Serial.println(F("Starting LoRa Successful!"));
}
}
void loop() {
LoRa_Packet_Sender();
GoToSleep();
}
// LoRa Task
void LoRa_Packet_Sender() {
Serial.print(F("Sending packet: "));
Serial.println(message);
// send packet
LoRa.beginPacket();
LoRa.print(message);
LoRa.endPacket();
// Sleep Task
void GoToSleep(){
Serial.println(F("MKR WAN 1310 - Going in Sleep"));
LowPower.deepSleep(20000);
}
It is important to know that the Low Power task applies only to microcontroller. This means
that external modules such as Murata module we used here, found on MKR WAN 1310
board, must be coded in the task separately to make the module go into sleep state.
If external modules such as Murata (LoRa) Module and sensors are to be used, please
remember to put into sleep state before making the MCU go into sleep. Otherwise the device
will not go into complete sleep state and maximum power saving will not be possible. This
includes turning off for example peripheral interfaces such as TWI and SPI.
COPY
Explain
/*
Low Power - Low Voltage Detection - SAMD21 Specific Configuration Example
*/
void setup() {
Serial.begin(57600);
delay(100);
void loop() {
// Reading from the Battery Pin
voltValue = analogRead(A0);
Serial.print(F("Battery: "));
Serial.print(battery_percentage);
Serial.println(F(" % "));
delay(2000);
The analogReference() is used to configure the reference voltage for analog input.
The analogReadResolution() is used to determine the resolution of the value returned by
analogRead().
The analogRead() is used to set an analog pin to read the value from.
Last but not least, the respective resolution divider value. For the present example it uses
4095 for 12-Bit resolution applicable for MKR WAN 1310. If it uses different resolution,
such as 10-Bits, you will need to define it to 1023.
If voltage divider is to be used for the application, it is necessary to define and set the resistor
relationship inside the code to be able to obtain correct value.
*For more advanced low voltage detection please see the last section of the presente guide
about Advanced Low-Power Techniques in Arduino*
The processor usually will be changing the frequency depending on the workloads. This
processor frequency varies as to speed up the compute process in a minimal amount of time.
Some modules such as Wi-Fi and Bluetooth® Low Energy requires minimum frequency
level. Otherwise, operating at even lower frequencies will result in modules not working
correctly, or not run in any instance. At some instances, going further low frequency requires
external crystal oscillator as it will require matching oscillator.
So forcefully lowering the frequency will not be of help as it will scramble the modules or the
operations. While leaving the frequency unrestricted or unlimited also won't help provide
long battery life. It is recommended to set frequency levels depending on the software
architecture to maintain power consumption while accomplishing designed tasks. Frequency
levels can be referenced via datasheet of the board, so it can used to design the software
architecture and improve power consumption.
With the voltage, commonly the boards can defined at 5V or at 3.3V of operating voltage.
This operating voltage usually depends on external components which will be interfaced to
work with the central boards, in which it will be Arduino boards. However, not always the
requirements of external components or such similar cases require high voltage. So in cases
like this, it is possible to set operating voltage of 3.3V. Providing much better chance of
having power efficient device. The power consumption beginning with these 2 factors can
reduce or even halve the overall power consumption.
The Power Reduction mode is tied to Non-Volatile Memory Controller and CTRLB register
is used to control its mode. The Power Reduction Mode bit is set in Status Register
(STATUS.PRM), when the Non-Volatile Memory controller and block is in power reduction
mode. The Set and Clear Power Reduction Mode commands are used to take in and out the
Non-Volatile Memory controller and block. For register table visualization, you can see
following references and the datasheet for more detail.
This Power Reduction Register can turn off several things inside the Microcontroller. It can
cut off TWI, SPI, USART0, timers and counters, and Analog-to-Digital Converter. If
Analog-to-Digital Converter bit is to be turned off, ADCRSA bust be set to Zero as it will
stay frozen in an active state.
Peripheral interfaces such as SPI, I2C, Serial communications are used regularly to establish
bridge with sensors. Having the overall design requirement, it will be known that some
peripherals will not be implemented in the application. Instead of leaving peripherals floating,
it is good practice to turn off these peripherals that are not going to be used to save some
power consumption. On top of it, the sensors also have the capability of putting themselves
into low power state if commanded to do so. Via established peripherals, commands can be
sent to turn the sensors into low power state before cutting off peripherals to get into sleep.
ADC (Analog to Digital Converter) can be used as wake up source as previously discussed.
But it can also be turned off to save power consumption if it will not be used as a wakeup
source. This peripherals sometimes drain significant amount of power, so turning off may
help to gain extra power to attain longer battery life.
The Brown-out Detection and Watchdog timer are internal modules which can be manually
controlled, if required, to ahieve even lower power consumption. Brown-out detection usually
is comparison purposes for when the processor requires low voltage detection. The Watchdog
timer is crucial as it monitors the microcontroller operation state.
If the microcontroller ever gets stuck and stops or goes through illegal process, the watchdog
timer will be triggered to reset and put the microcontroller back on track. On the other hand,
this same watchdog timer can be disabled temporarily to save power consumption. This is
always recommended to be implemented after the software is verified to be stable enough.
Otherwise, it won't help reduce power consumption and break the system.
External Devices
If there are external devices such as SD devices and sensors, these can be turned off by
adding Mosfets in between. The MOSFET can help to cut off the power for SD devices
connected to the Arduino board to reduce power consumption if the device ever goes into
deep sleep mode for example. In case of sensors, if it is required to completely save power
consumption, can be cut off with MOSFET. This method is helpful when device periodically
goes into sleep state, so it can save the power consumption to its fullest.
Low-Dropout Regulator
Some designs require voltage regulators for it to work properly. If the design requires the use
of voltage regulator, try to look for Low-Dropout regulators with low Quiescent Current. This
will help the device to be on a much lower power consumption level when it is in sleep state.
These types of details can be found on the datasheets of regulator manufacturers.
Power Budgeting
It is good practice to keep record of power budget of the system. As it provides an overview
and details to analyze how the power distribution is handled throughout the whole design.
This can help to design better power consumption as it might give you information about
possible sleep state timer increase for example.
You can use the following battery life calculator to obtain estimated lifetime:
https://www.omnicalculator.com/other/battery-life
For this we will use power management method of avr microcontrollers, in which
Atmega328P are based of and found in Arduino Nano and Classic Family for example. The
low voltage detection method developed by Nick Gammon, Retrolefty, and Coding Badly,
will be combined with that is capable of going into low power state to save power.
COPY
Explain
// Nick Gammon
// Code courtesy of "Coding Badly" and "Retrolefty" from the Arduino forum
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>
const long InternalReferenceVoltage = 1062; // Adjust this value to your board's specific
internal BG voltage
void setup() {
Serial.begin(57600);
delay(100);
void loop() {
// Tuning on peripherals, timers, and ADC
manual_periph_ctrl(1);
/*
* Low Voltage Detection Task
*/
// Nick Gammon
// Code courtesy of "Coding Badly" and "Retrolefty" from the Arduino forum
// results are Vcc * 100
// So for example, 5V would be 500.
int getBandgap(){
// REFS0 : Selects AVcc external reference
// MUX3 MUX2 MUX1 : Selects 1.1V (VBG)
ADMUX = bit (REFS0) | bit (MUX3) | bit (MUX2) | bit (MUX1);
ADCSRA |= bit( ADSC ); // start conversion
while (ADCSRA & bit (ADSC)){
} // wait for conversion to complete
int results = (((InternalReferenceVoltage * 1024) / ADC) + 5) / 10;
return results;
} // end of getBandgap
/*
* Low Power Related Tasks
*/
// Enabling Watchdog Timer
void WatchdogEnable() {
// clear various "reset" flags
MCUSR = 0;
// allow changes, disable reset
WDTCSR = bit (WDCE) | bit (WDE);
// set interrupt mode and an interval
WDTCSR = bit (WDIE) | bit (WDP3) | bit (WDP0); // set WDIE, and 8 seconds delay
wdt_reset(); // pat the dog
// disable ADC
ADCSRA = 0;
// ready to sleep
set_sleep_mode (SLEEP_MODE_PWR_DOWN); // Should throw ~0.4mA at best case
noInterrupts();
sleep_enable();
// turn off brown-out enable in software
MCUCR = bit (BODS) | bit (BODSE);
MCUCR = bit (BODS);
interrupts(); // Guarantees next instruction executed
sleep_cpu ();
void i2c_switch_off(){
// turn off I2C
TWCR &= ~(bit(TWEN) | bit(TWIE) | bit(TWEA));
} // end of goToSleep
if (selector == 0){
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();
ADCSRA = old_ADCSRA;
}
Explain
// Find internal 1.1 reference voltage on AREF pin
void setup ()
{
ADMUX = bit (REFS0) | bit (REFS1);
}
void loop () { }
Using analogReference and analogRead functions to obtain the value.
COPY
Explain
// Find internal 1.1 reference voltage on AREF pin
void setup ()
{
analogReference (INTERNAL);
analogRead (A0); // force voltage reference to be turned on
}
void loop () { }
To read more about the Low Power systems topic In-Depth, you can check the following
link: https://www.gammon.com.au/power
Last revision08/02/2024
A potentiometer is a simple mechanical device that comes in many different forms. It
provides a variable amount of resistance that changes as you manipulate it. The examples in
this article uses a potentiometer with a twisting shaft, one of the more common versions of a
potentiometer you will find.
The typical potentiometer will have 3 pins, two power supply pins (+5V and GND), and one
pin that connects to an analog input pin on your Arduino to read the value output.
To learn how to read data from a potentiometer, and display it in the Serial Monitor, visit the
Analog Read Serial example.
Hardware Required
Arduino board
Potentiometer
1x Red, 1x Green, 1x Blue LED
3x 220 Ohm Resistors
Circuit
Potentiometer + Pin to 5V
Potentiometer - Pin to GND
Potentiometer Data Pin to A3
A Red LED connected to pin 9 with a 220 Ohm Resistor
A Green LED connected to pin 10 with a 220 Ohm Resistor
A Blue LED connected to pin 11 with a 220 Ohm Resistor
Complete Circuit
Complete Circuit
COPY
Explain
/*
* Code for making one potentiometer control 3 LEDs, red, grn and blu, or one tri-color LED
* The program cross-fades from red to grn, grn to blu, and blu to red
* Clay Shirky <clay.shirky@nyu.edu>
*/
// OUTPUT: Use digital pins 9-11, the Pulse-width Modulation (PWM) pins
// LED's cathodes should be connected to digital GND
int redPin = 9; // Red LED, connected to digital pin 9
int grnPin = 10; // Green LED, connected to digital pin 10
int bluPin = 11; // Blue LED, connected to digital pin 11
// Program variables
int redVal = 0; // Variables to store the values to send to the pins
int grnVal = 0;
int bluVal = 0;
void setup()
{
pinMode(redPin, OUTPUT); // sets the pins as output
pinMode(grnPin, OUTPUT);
pinMode(bluPin, OUTPUT);
}
// Main program
void loop()
{
potVal = analogRead(potPin); // read the potentiometer value at the input pin
Explain
/*
* Code for cross-fading 3 LEDs, red, green and blue (RGB)
* To create fades, you need to do two things:
* 1. Describe the colors you want to be displayed
* 2. List the order you want them to fade in
*
* DESCRIBING A COLOR:
* A color is just an array of three percentages, 0-100,
* controlling the red, green and blue LEDs
*
* Red is the red LED at full, blue and green off
* int red = { 100, 0, 0 }
* Dim white is all three LEDs at 30%
* int dimWhite = {30, 30, 30}
* etc.
*
* Some common colors are provided below, or make your own
*
* LISTING THE ORDER:
* In the main part of the program, you need to list the order
* you want to colors to appear in, e.g.
* crossFade(red);
* crossFade(green);
* crossFade(blue);
*
* Those colors will appear in that order, fading out of
* one color and into the next
Last revision08/02/2024
This article was revised on 2022/01/18 by Karl Söderby.
Motors and transistors are very common electronic components. This article aims to provide
some of the basics, along with a working code example, circuits and schematics.
When a pushbutton connected to digital pin 2 is pressed, the Arduino will control a transistor
via pulse-width modulation (PWM), which will ramp up the motor's speed, then slow it back
down.
About Transistors
The Arduino can only provide 40mA at 5V on its digital pins. Most motors require more
current and/or voltage to operate. A transistor can act as a digital switch, enabling the
Arduino to control loads with higher electrical requirements. The transistor in this example
completes the motor's circuit to ground. This example uses a TIP120, which can switch up to
60V at 5A.
When PWMing a transistor, it's similar to pulsing an LED. The higher the PWM value, the
faster the motor will spin. The lower the value, the slower it will spin.
Transistors have three pins. For Bipolar Junction Transistors (BJT), like the one used used in
this example, the pins are called base, collector, and emitter. A small amount of current on
the base pin closes a circuit between the collector and emitter pins. BJTs come in two
different types, NPN and PNP. The TIP120 is a NPN-type transistor, which means the
collector will connect to the motor, and the emitter will connect to ground.
About Motors
Motors work through a process called induction. When you an put electric charge through
wire, a magnetic field is created. A coiled wire will create a stronger field, as will increased
current. In a DC motor, a coiled wire surrounds the motor's shaft. The generated magnetic
field is pulled and repulsed by magnets inside the motor's body.
When a motor stops, there is the potential for a small amount of current to be generated as the
shaft continues spinning. A diode placed in parallel with the motor leads will keep any
generated electricity from damaging your circuit.
Motors will pull the most current when they start up, or have a load. The stall current is the
amount of current a motor will pull when it is stopped by a force. When a motor is up and
running, it will pull significantly less current.
The voltage rating describes the peak operating voltage for a motor, when it works at
optimum efficiency. Going over or under the motor's rated voltage will, over time, shorten
the motor's life. If you provide less than the rated voltage, the motor will spin more slowly.
Typically, a motor needs about 1/2 its rated voltage to run. If you provide less than that when
starting up, it probably won't begin to move.
Hardware Required
Arduino Board
A momentary switch or button
10k ohm resistor
Breadboard
Hook-up wire
9V DC motor
TIP120 transistor
1N4001 diode
9V battery
Circuit
Transistor Motor Control circuit.
Transistor Motor Control circuit.
First, connect wires for power and ground. In the illustration, the red (power) and black
(ground), connect to the two long vertical rows on the side of the breadboard. This provides
access to the 5 volt supply and ground.
Place the pushbutton on the breadboard, straddling the center. A wire connects digital pin 2 to
one leg of the pushbutton. That same leg of the button connects through a 10-kilohm pull-
down resistor to ground. The leg of the button not connected to the Arduino should be wired
to the 5 volt supply.
Connect pin 9 on the Arduino to the base pin of the TIP120. If you are looking at the
transistor so that the metal tab is facing away from you, the base pin is on the left side of the
transistor. This is the pin that will control open or close. The transistor's collector connects to
one lead of the motor, the emitter to ground.
The other end of the motor connects to the positive lead of the 9V battery. Connect the
battery's ground to the Arduino's ground.
Schematic
Transistor Motor Control schematic.
Transistor Motor Control schematic.
Code Example
Below is the full program for controlling a DC motor with a transistor. Further down this
article, you will find a more detailed explanation.
COPY
Explain
/*
Motor Control with a Transistor
The circuit :
* momentary switch with one end connected to 5V, the other end connected
to GND through a 10-kilohm resistor, and digital pin 2.
* TIP120 tranistor, with the Base connected to digital pin 9, the Emitter to ground,
and the Collector to one lead from a 9V DC motor
* a 9V battery, with the ground connected to the Arduino's ground, and the power
connected to the motor
* 1N4001 diode across the motor's leads, with the striped side connected to the 9V
The Arduino can only provide 40mA at 5V on its pins. Most motors require
more current and/or voltage to overcome inertia and run. A transistor
can act as a digital switch, enabling the Arduino to control loads with
higher electrical requirements.
https://docs.arduino.cc/learn/electronics/transistor-motor-control
// the transistor which controls the motor will be attached to digital pin 9
int motorControl = 9;
First, create a pair of variables for the pushbutton's state and the motor control pin :
COPY
int pushButton = 2;
int motorControl = 9;
In the setup(), declare these pins as an input and output, respectively :
COPY
void setup() {
pinMode(pushButton, INPUT);
pinMode(motorControl, OUTPUT);
}
Now that your setup has been completed, move into the loop().
COPY
void loop() {
Read the state of the pushbutton and check if it is HIGH. It's possible to make the evaluation
directly in your if() statement like this :
COPY
if(digitalRead(pushButton) == HIGH){
If the button is pressed, ramp up the speed of the motor by increasing the PWM value of the
motorControl pin. Once it has reached full speed, ramp back down:
COPY
Explain
for(int x = 0; x <= 255; x+=5){
analogWrite(motorControl, x);
delay(50);
}
COPY
}
delay(1);
}
Powering Alternatives
Arduino boards have five options in which they can be powered:
Powering via the onboard barrel jack connector (if available on the board)
Powering via the onboard battery connector (if available on the board)
*Powering your board via the 3V3/5V pins is not recommended, as it can damage your
board's voltage regulator. Read more here.
USB Connector
The most common and easiest way we can power an Arduino board is by using its onboard
USB connector. The USB connector provides a regulated 5V line to power the board's
electronics. However, 5V from the USB connector can also power external components
through the 5V pin that can be found in Arduino boards.
The voltage line from the barrel jack connector is regulated in Arduino boards using their
onboard voltage regulator; usually, it is first regulated to 5V and then regulated again to 3V3
in most Arduino boards. The recommended voltage and current ratings for external regulated
DC power supplies connected to the barrel jack connector are summarized in the table below:
Board External Power Supply Voltage (V) External Power Supply Current (A)
Arduino UNO Rev3 7-12 1
Arduino UNO WiFi Rev2 7-12 1.5
Arduino Leonardo 7-12 1
Arduino Mega 2560 Rev3 7-12 1
Arduino Due 7-12 1.5
Arduino Zero 5-18 1
Battery Connector
Some Arduino boards have an onboard battery connector to connect a battery to the board
and use it as its primary or secondary power supply. The Arduino boards with an onboard
battery connector are the following:
Arduino Portenta H7
Arduino Nicla Sense ME
Arduino Nicla Vision
Arduino MKR NB 1500
Arduino MKR Vidor 4000
Arduino MKR WiFi 1010
Arduino MKR ZERO
Arduino MKR WAN 1310
Arduino MKR GSM 1400
Pro family boards use a 3-pin, 1.2mm SMD ACH battery connector; MKR family boards use
a 2-pin, 2mm SMD PH battery connector.
The boards mentioned before have an onboard integrated battery charge management circuit.
This circuit integrates the most common battery and power management functions, like a
battery charger, a voltage regulator, and a load switch, all in one.
Arduino boards with an onboard battery connector can work with single cell 3V7 Li-Ion and
Li-polymer batteries.
VIN Pin
The VIN pin in Arduino boards is a power pin with a dual function. This pin can work as a
voltage input for regulated external power supplies that do not use a barrel jack connector.
This pin can also work as a voltage output when an external power supply is connected to the
barrel jack connector present in some Arduino boards. An important consideration is that the
VIN pin is connected directly to the input pin of the onboard voltage regulator on Arduino
boards. Since the VIN pin is directly connected to the voltage regulator, the VIN pin does not
have reverse polarity protection.
Use the VIN pin carefully to avoid damaging your Arduino board since it does not have
reverse polarity protection.
The minimum and maximum voltages that can be applied to the VIN pin are determined by
the onboard voltage regulator on Arduino boards, varying from board to board. Those
voltages are summarized in the table below:
Since 3V3 and 5V pins are directly connected to the onboard's 3V3 and 5V voltage regulators
outputs, these pins have no reverse polarity protection. Use them carefully when working as
power inputs to avoid damaging your board's voltage regulator.
Although 3V3 and 5V pins can be used as power inputs, it is not recommended if no power
supply is connected through the USB port, the barrel jack connector, or the VIN pin. 3V3 and
5V pins are connected directly to the onboard voltage regulator's output pin. Suppose the
voltage in the voltage regulator output pin becomes higher than the input voltage of the
voltage regulator. In that case, a large current may flow into the voltage regulator from its
output pin to its input pin. That large current can permanently damage your board's voltage
regulator.
It is safe, but not recommended, to apply a voltage to the 3V3 or 5V pins that are not higher
than the input voltage of the voltage regulators.
The maximum current that can be drawn from the 3V3 and 5V pins when working as power
outputs are summarized below. Notice that these currents can be provided by the 3V3 and 5V
onboard voltage regulators, or from the power source connected to the board:
Board 5V Pin Output Current (A) 3V3 Pin Output Current (A)
UNO Mini 1 0.5
UNO Rev3 1 0.15
UNO WiFi Rev2 1 0.5
UNO Rev3 SMD 1 0.15
Leonardo 1 0.15
Mega 2560 Rev3 0.8 0.05
Micro 1 0.15
Zero 1 0.5
Portenta H7 - 1
Nicla Sense ME* - 0.5
Nano RP2040 Connect - 1
MKR NB 1500 - 0.5
MKR Vidor 4000 1 0.3
MKR WiFi 1010 1 0.5
MKR Zero - 0.5
MKR1000 WIFI - 0.5
MKR WAN 1300 - 0.5
MKR WAN 1310 - 0.5
Nano 0.8 0.15
Nano Every 1 0.5
Nano 33 IoT 1 0.5
Nano 33 BLE - 1
Nano 33 BLE Sense - 1
Choosing a Power Input
Now that we know more about the powering alternatives of Arduino boards, we can answer
that question we made at the beginning of this article about what power connector or pin we
should use. When choosing a power connector or pin for a specific application or project, we
should consider the available power source and the power budget of our application or
project.
A power budget analyzes how much power our application or project requires for its correct
working.
Let's talk about when it is recommended to use each of the options ways in which Arduino
boards can be powered:
USB Connector
This option is often recommended when experimenting with small loads that require 5V; the
current would be constrained by the USB host device where the board is connected.
Battery Connector
This option is recommended for portable projects or projects that need a secondary o backup
power supply. Currently, 3V7 Li-Ion and Li-polymer batteries are supported only; the
battery's capacity constrains current.
VIN Pin
This option is recommended when a regulated power supply without a barrel jack connector
is available. Take into account that using VIN pin should be made carefully since this pin
does not have reverse polarity protection. Current is constrained by the regulated power
supply and the onboard voltage regulator.
3V3/5V Pin
Avoid this option since the risk of damaging the onboard voltage regulator is high. It can be
done safely if the applied voltage to the 3V3 or 5V pins is not higher than the input voltage of
the voltage regulators.
Power supplies are one of the most popular and most needed electronic testing equipment.
Check out more about them in this guide from BK Precision®.
LiPo batteries are everywhere. Check out this guide from Roger's Hobby Center to learn
more about LiPo batteries.
AuthorKarl Söderby
Last revision08/02/2024
All electronic devices, including Arduino boards, consume power. The power consumption is
measured in ampere-hours (Ah), and with low-voltage devices, it is typically measured in
mAh.
When creating projects that run on a battery or are power-constrained, taking power
consumption into account can be critical. It will among other things help you decide which
kind of battery you need to use.
In this article, we will demonstrate how you can perform power consumption tests, using a
power profiler. A power profiler is used to measure consumption over a specific time, where
we record several thousand samples. You will find instructions for both the hardware setup,
the software setup, and the actual power consumption tests in this article.
When connected, the power profiler can measure the power consumption of your board with
high accuracy. Any power that your board consumes can now be detected, and with a
software tool (such as the nRF Connect for Desktop), we can record power consumption over
time.
So what is it that we are measuring? In very simple terms, all electronic devices draw current,
whether it is small or big. A small LED can for example draw 10 mA (0.01 A), while a servo
motor can draw up towards 1000 mA (1 A). If you have an LED on for an hour that draws 10
mA, we can express it as mAh, which means milli-ampers consumed per hour.
Now, if we wanted to power this application using a 300 mAh battery we need to calculate
the time with the following formula:
Formular
Formular
With that information, we can make an educated guess of what type of battery we should get.
For example, a battery with more capacity, let's say 600 mAh, would in theory last for twice
the period.
Note that there are other factors at play, such as the battery's discharge rate and the general
quality of the battery. The above formulas and measurements are to be considered guidelines.
Software Setup
The software setup involves two steps: uploading a sketch and installing nRF Connect for
Desktop.
Upload Sketch
This step is rather straightforward. Upload the sketch that you want to measure the power
consumption of. Below is a minimal sketch that reads an analog pin continuously.
COPY
Explain
void setup() {}
void loop() {
int analog_value = analogRead(A0);
delay(1);
}
Install Desktop App
To measure the power consumption, we are going to use the nRF Connect for Desktop tool.
This is a program that you install on your computer.
Hardware Setup
The profiler we used is the Power Profiler Kit II.
First, disconnect the USB cable from your board. You will be powering the board directly
from the power profiler, so there's no need for the USB cable at this point.
Use the provided cable from the kit, and connect it to your board's GND and power pin,
following the illustration below:
Connect the power profiler to the board.
Connect the power profiler to the board.
Important note! In the software setup, you enable the "Power Output" of the power profiler.
Make sure that the voltage (3.3 V or 5 V) matches the voltage on the power pin of the board.
Applying 5 V to a 3.3 V pin will damage your board.
Enable the power output, by clicking the "Enable Power Output" option.
Start sampling.
Start sampling.
During the test, you can see the power consumption in real-time. After 60 seconds (or when
the specified sample period ends), you will have the data, which includes the max and avg
consumption. You can also zoom in to view the data.
Example Results
In this section, you will find a number of tests we ran on a set of Arduino boards during
different conditions.
COPY
Explain
void setup() {}
void loop() {
int analog_value = analogRead(A0);
delay(1);
}
In the table below, you can see the results of each board tested with the sketch:
COPY
Explain
#include "thingProperties.h"
void setup() {
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
// Defined in thingProperties.h
initProperties();
setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
ArduinoCloud.update();
digitalWrite(LED_BUILTIN, led);
}
/*
Since Led is READ_WRITE variable, onLedChange() is
executed every time a new value is received from IoT Cloud.
*/
void onLedChange() {
Serial.print("Led status changed:");
Serial.println(led);
}
In the table below, you can see the results of each board tested with the sketch:
Overview
This section provides an overview of the topics covered in the article.
What Is I2C?
Arduino I2C Pins
I2C Wiring
Wire Library
Examples
What Is I2C?
The I2C protocol involves using two lines to send and receive data: a serial clock pin (SCL)
that the Arduino Controller board pulses at a regular interval, and a serial data pin (SDA)
over which data is sent between the two devices.
In I2C, there is one controller device, with one or more peripheral devices connected to the
controllers SCL and SDA lines.
As the clock line changes from low to high (known as the rising edge of the clock pulse), a
single bit of information is transferred from the board to the I2C device over the SDA line.
As the clock line keeps pulsing, more and more bits are sent until a sequence of a 7 or 8 bit
address, and a command or data is formed. When this information is sent - bit after bit -, the
called upon device executes the request and transmits it's data back - if required - to the board
over the same line using the clock signal still generated by the Controller on SCL as timing.
Each device in the I2C bus is functionally independent from the controller, but will respond
with information when prompted by the controller.
Because the I2C protocol allows for each enabled device to have it's own unique address, and
as both controller and peripheral devices to take turns communicating over a single line, it is
possible for your Arduino board to communicate (in turn) with many devices, or other
boards, while using just two pins of your microcontroller.
An I2C Message
An I2C Message
The controller sends out instructions through the I2C bus on the data pin (SDA), and the
instructions are prefaced with the address, so that only the correct device listens.
Then there is a bit signifying whether the controller wants to read or write.
Every message needs to be acknowledged, to combat unexpected results, once the receiver
has acknowledged the previous information it lets the controller know, so it can move on to
the next set of bits.
8 bits of data
Another acknowledgement bit
8 bits of data
Another acknowledgement bit
But how does the controller and peripherals know where the address, messages, and so on
starts and ends? That's what the SCL wire is for. It synchronises the clock of the controller
with the devices, ensuring that they all move to the next instruction at the same time.
However, you are nearly never going to actually need to consider any of this, in the Arduino
ecosystem we have the Wire library that handles everything for you.
Breakout Boards
Some brekout board modules let you wire them directly, with bare wires on a breadboard. To
connect a module like this to your Arduino board, connect it as follows:
Both Qwiic and STEMMA QT bundle together wires for power, ground, as well as the SDA
and SCL wires for I2C, making it a complete kit, one cable that bundles everything together.
Both Qwiic and STEMMA QT use I2C, and even when inspecting modules using the two
standards up close, it may be difficult to tell what makes them unique from each other. But
there is a difference! And it has some implications on how and for what you may use them.
Qwiic has level shifting and voltage regulation on the controller (but not on the peripherals).
What this means is that Qwiic is 3.3 V logic only. This makes it easier to use, as for the end
user, there is one less thing that can go wrong when designing and assembling your circuit.
STEMMA QT, on the other hand, doesn't have this. This lets you use both 3.3 V and 5 V
logic for modules. This also means that there is one more thing you may need to consider
when creating your circuit, but it also grants some more flexibility in power and logic
requirements.
The pin order for STEMMA QT is designed to match the pin order for Qwiic, enabling cross-
compatibility between the two standards.
Grove
Grove is another connector standard, this one developed by seeed studio. You can find a
plethora of modules with a Grove connector, however only some of them use I2C. There are
no Arduino boards that have a built in Grove connector, however you can use products such
as the MKR connector carrier, Nano Grove Shield, or the Base Shield from the Arduino
Sensor Kit to connect Grove sensors to your Arduino board.
Wire Library
The Wire library is what Arduino uses to communicate with I2C devices. It is included in all
board packages, so you don't need to install it manually in order to use it.
To see the full API for the Wire library, visit its documentation page.
An example of this is if you want to use Adafruits MCP9808 sensor module, you download
the Adafruit_MCP9808 Library from the IDEs library manager, which enables you to use
functions such as tempsensor.readTempC() in order to read the sensors temperature data by
requesting from the right address, and read the information returned with just a single line
instead of writing the Wire code yourself.
To learn how to install libraries, check out our guide to installing libraries.
Examples
The remainder of this article is a collection of examples that can get you off the ground with
I2C.
Controller Reader
Arduino Boards connected via I2C
Arduino Boards connected via I2C
In some situations, it can be helpful to set up two (or more!) Arduino boards to share
information with each other. In this example, two boards are programmed to communicate
with one another in a Controller Reader/Peripheral Sender configuration via the I2C
synchronous serial protocol. Several functions of Arduino's Wire Library are used to
accomplish this. Arduino 1, the Controller, is programmed to request, and then read, 6 bytes
of data sent from the uniquely addressed Peripheral Arduino. Once that message is received,
it can then be viewed in the Arduino Software (IDE) serial monitor window.
COPY
Explain
// Wire Controller Reader
// by Nicholas Zambetti [http://www.zambetti.com](http://www.zambetti.com)
#include <Wire.h>
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(9600); // start serial for output
}
void loop() {
Wire.requestFrom(8, 6); // request 6 bytes from peripheral device #8
delay(500);
}
Peripheral Sender Sketch
COPY
Explain
// Wire Peripheral Sender
// by Nicholas Zambetti [http://www.zambetti.com](http://www.zambetti.com)
#include <Wire.h>
void setup() {
Wire.begin(8); // join i2c bus with address #8
Wire.onRequest(requestEvent); // register event
}
void loop() {
delay(100);
}
In some situations, it can be helpful to set up two (or more!) Arduino boards to share
information with each other. In this example, two boards are programmed to communicate
with one another in a Controller Writer/Peripheral Receiver configuration via the I2C
synchronous serial protocol. Several functions of Arduino's Wire Library are used to
accomplish this. Arduino 1, the Controller, is programmed to send 6 bytes of data every half
second to a uniquely addressed Peripheral. Once that message is received, it can then be
viewed in the Peripheral board's serial monitor window opened on the USB connected
computer running the Arduino Software (IDE).
COPY
Explain
// Wire Master Writer
// by Nicholas Zambetti [http://www.zambetti.com](http://www.zambetti.com)
#include <Wire.h>
void setup()
{
Wire.begin(); // join i2c bus (address optional for master)
}
byte x = 0;
void loop()
{
Wire.beginTransmission(4); // transmit to device #4
Wire.write("x is "); // sends five bytes
Wire.write(x); // sends one byte
Wire.endTransmission(); // stop transmitting
x++;
delay(500);
}
Peripheral Receiver Sketch
COPY
Explain
// Wire Peripheral Receiver
// by Nicholas Zambetti [http://www.zambetti.com](http://www.zambetti.com)
#include <Wire.h>
void setup()
{
Wire.begin(4); // join i2c bus with address #4
Wire.onReceive(receiveEvent); // register event
Serial.begin(9600); // start serial for output
}
void loop()
{
delay(100);
}
This code lets you read accelerometer data from a Grove 6-Axis Accelerometer module using
the seeed arduino LSM6DS3 library.
COPY
Explain
#include "LSM6DS3.h"
#include "Wire.h"
if (accelerometer.begin() != 0) {
Serial.println("LSM6DS3 not found, check your wiring.");
} else {
Serial.println("LSM6DS3 found!");
}
}
void loop() {
//Gyroscope
Serial.print("\nGyroscope:\n");
Serial.print(" X1 = ");
Serial.println(accelerometer.readFloatGyroX(), 4);
Serial.print(" Y1 = ");
Serial.println(accelerometer.readFloatGyroY(), 4);
Serial.print(" Z1 = ");
Serial.println(accelerometer.readFloatGyroZ(), 4);
//Accelerometer
Serial.print("\nAccelerometer:\n");
Serial.print(" X1 = ");
Serial.println(accelerometer.readFloatAccelX(), 4);
Serial.print(" Y1 = ");
Serial.println(accelerometer.readFloatAccelY(), 4);
Serial.print(" Z1 = ");
Serial.println(accelerometer.readFloatAccelZ(), 4);
delay(1000);
}
I2C BMP280
Qwiic BMP280 module
Qwiic BMP280 module
This code example lets you read the temperature over I2C from a BMP280 breakout module
from Adafruit:
COPY
Explain
#include <Wire.h>
#include <Adafruit_BMP280.h>
void setup() {
Serial.begin(9600);
void loop() {
// Read the values
float temperature = bmp.readTemperature();
Serial.println();
delay(2000);
}
I2C OLED
Grove OLED over I2C
Grove OLED over I2C
This code example draws a version of the Arduino logo on a 128x64 I2C Grove OLED:
COPY
Explain
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 not found"));
while(1){}
}
display.clearDisplay();
void loop() {
}
AuthorArduino
Last revision08/02/2024
This article was revised on 2021/11/18 by Karl Söderby.
CIPO (Controller In Peripheral Out) - The Peripheral line for sending data to the Controller
COPI (Controller Out Peripheral In) - The Controller line for sending data to the peripherals
SCK (Serial Clock) - The clock pulses which synchronize data transmission generated by the
Controller and one line specific for every device
CS (Chip Select) - the pin on each device that the Controller can use to enable and disable
specific devices. When a device's Chip Select pin is low, it communicates with the
Controller. When it's high, it ignores the Controller. This allows you to have multiple SPI
devices sharing the same CIPO, COPI, and SCK lines.
To write code for a new SPI device you need to note a few things:
What is the maximum SPI speed your device can use? This is controlled by the first
parameter in SPISettings. If you are using a chip rated at 15 MHz, use 15000000. Arduino
will automatically use the best speed that is equal to or less than the number you use with
SPISettings.
Is data shifted in Most Significant Bit (MSB) or Least Significant Bit (LSB) first? This is
controlled by second SPISettings parameter, either MSBFIRST or LSBFIRST. Most SPI
chips use MSB first data order.
Is the data clock idle when high or low? Are samples on the rising or falling edge of clock
pulses? These modes are controlled by the third parameter in SPISettings.
The SPI standard is loose and each device implements it a little differently. This means you
have to pay special attention to the device's datasheet when writing your code.
Generally speaking, there are four modes of transmission. These modes control whether data
is shifted in and out on the rising or falling edge of the data clock signal (called the clock
phase), and whether the clock is idle when high or low (called the clock polarity). The four
modes combine polarity and phase according to this table:
Mode Clock Polarity (CPOL) Clock Phase (CPHA) Output Edge Data Capture
SPI_MODE0 0 0 Falling Rising
SPI_MODE1 0 1 Rising Falling
SPI_MODE2 1 0 Rising Falling
SPI_MODE3 1 1 Falling Rising
Once you have your SPI parameters, use SPI.beginTransaction() to begin using the SPI port.
The SPI port will be configured with your all of your settings. The simplest and most
efficient way to use SPISettings is directly inside SPI.beginTransaction(). For example:
COPY
SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0));
If other libraries use SPI from interrupts, they will be prevented from accessing SPI until you
call SPI.endTransaction(). The SPI settings are applied at the begin of the transaction and
SPI.endTransaction() doesn't change SPI settings. Unless you, or some library, calls
beginTransaction a second time, the setting are maintained. You should attempt to minimize
the time between before you call SPI.endTransaction(), for best compatibility if your program
is used together with other libraries which use SPI.
With most SPI devices, after SPI.beginTransaction(), you will write the Chip Select pin
LOW, call SPI.transfer() any number of times to transfer data, then write the CS pin HIGH,
and finally call SPI.endTransaction().
For more on SPI, see Wikipedia's page on SPI.
Tutorials
Extended SPI Library Usage with the Arduino Due
Introduction to the Serial Peripheral Interface
Digital Potentiometer Control (SPI)
Barometric Pressure Sensor (SPI)
AuthorJosé Bagur
Last revision08/02/2024
Arduino and Low-Power, Wide-Area Networks.
Arduino and Low-Power, Wide-Area Networks.
The exponential growth of the Internet of Things (IoT) and machine-to-machine (M2M)
communications in the last few years has had an impact on almost every aspect of our daily
lives. It is expected that by 2025 more than 75 billion IoT devices will be connected and
working around the world. But how are these IoT devices connected to the Internet?
Generally speaking, IoT and M2M applications and devices have low-power and low-data
transmission requirements (the data usually comes from sensors). Until recently, the
technologies used for connecting these applications and devices were not the ideal ones for
IoT applications, as shown in in the image below. For example, wireless personal area
networks (WLANs) and Bluetooth® were designed primarily for medium to high-speed data
communication in short-range environments. Wireless cellular networks such as 2G, 3G, 4G,
and 5G, were designed for high-speed data communication in medium-range environments.
These networks are usually employed in voice, data, and video communication. In order to
meet the particular requirements of IoT and M2M applications and devices, something had to
evolve to meet the particular requirements of IoT and M2M applications and devices.
The term LPWAN, which emerged in 2013, stands for low-power wide-area network. This is
a generic term that describes a group of network technologies designed to communicate small
data packets on low transmission data rates wirelessly, over relatively long distances using
lower power than common network technologies (like WLANs or Bluetooth, for example).
Transmission of small packets of data, low-power consumption, and wide-signal coverage are
ideal characteristics for IoT and M2M devices and applications.
LoRa is a physical layer technology that works in unlicensed ISM frequency bands. It´s based
on the chirped spread spectrum (CSS) technique. LoRa is basically a single-hop technology,
which relays the messages received from LoRa sensor nodes to a central server via gateways.
The data transmission rate supported by Lo Ra varies from 300 bps to 50 kbps. To support
LoRa on the Internet, The LoRa Alliance has developed LoRaWAN®, which includes
network and upper layer functionalities. LoRaWAN® provides three classes of end devices to
address the different requirements of a wide range of IoT applications.
LTE-M is compatible with existing LTE networks, it provides extended coverage comparable
to LTE networks, coverage for M2M applications similar to 5G networks, and offers a
seamless path towards 5G M2M solution. LTE-M is focused on providing variable data rates
and support for both real-time and non-real time applications. It supports low latency
applications, as well as deferred traffic applications that can operate with latencies in the
range of a few seconds. It has low power requirements and supports operations ranging from
low bandwidth to bandwidth as high as 1Mbps. LTE-M also supports devices with a very
wide range of message sizes.
NB-IoT can coexist with GSM and LTE in the licensed frequency bands of 700MHz,
800MHz, and 900MHz. NB-IoT is designed by optimizing and reducing the functionalities of
LTE so that it can be used for infrequent data transmissions and with low power
requirements. The data rate supported by NB-IoT is 200 kbps for downlink and 20 kbps for
uplink. The maximum payload size for each message is 1600 Bytes.
LPWANs are best suited for applications requiring infrequent uplink message delivery of
smaller messages. Most LPWANs technologies also have downlink capabilities. With lower
power requirements, longer ranges and lower costs than traditional mobile networks,
LPWANs enable a number of M2M and IoT applications, many of which were previously
constrained by budgets and power issues.
The MKR WAN 1310 is a development board for experimenting with either LoRa® or
LoRaWAN®.
It can easily connect to The Things Network, a platform with LoRa® coverage all over the
world. It can also be used for point-to-point connection, where you can easily set up
communication between two MKR WAN boards.
Coverage
If you are planning to connect to The Things Network, you can check out the available
gateways at their LoRa® Gateway map.
You can also opt to set up your own gateway, which you can have several end-devices
connect to (building your own network).
MKRWAN library
To access the Murata CMWX1ZZABZ module onboard the MKR WAN 1300/1310, you can
refer to the MKRWAN library.
LoRa library
To easily set up communication between two MKR WAN 1310 boards, you can refer to the
examples in the LoRa library, credit to Sandeep Mistry.
Documentation
You can visit the official documentation for the MKR WAN boards through the links below:
The MKR NB 1500 is a development board designed for global use, providing connectivity
on LTE's Cat M1/NB1 bands 1, 2, 3, 4, 5, 8, 12, 13, 18, 19, 20, 25, 26, 28.
Operators offering service in that part of the spectrum include: Vodafone, AT&T, T-Mobile
USA, Telstra, and Verizon, among others.
Coverage
NB-IoT and LTE-M coverage includes many regions, such as Europe, North & South
America and large parts of Asia.
The MKR NB 1500 can be configured to connect to either NB-IoT or LTE-M, with the
option of choosing a preferred RAT (Radio Access Technology). It is possible to configure
the modem with a primary and backup network.
To see the full coverage, you can visit the NB-IoT / LTE-M coverage map.
MKR NB Library
To access the uBlox SARA-R410M-02B modem onboard the MKR NB 1500, you can refer
to the MKRNB library.
Documentation
You can visit the official documentation for this board at the MKR NB 1500 documentation
page.
The MKR FOX 1200 is a development board designed for LPWAN solutions using the
Sigfox infrastructure.
Every MKR FOX 1200 comes with a free one-year subscription to Sigfox' data plan, useful
for testing the Sigfox network. Please check the plan’s conditions in terms of messages per
day at the Sigfox website.
Coverage
The Sigfox network exists in different parts of the world, but it is mainly operating within
EU. Other countries that have coverage is USA, Japan, Australia, South Africa, New Zealand
and more.
Note that while there is coverage in countries outside EU, it is limited to certain areas within
the country.
To see the full coverage, you can visit Sigfox's own coverage map.
Sigfox Library
To access the Sigfox transceiver ATA8520, you can refer to the Sigfox library.
Documentation
You can visit the official documentation for this board at the MKR FOX 1200 documentation
page.
AuthorJosé Bagur
Last revision08/02/2024
With the help of a smartphone, we can know where we are on the earth to a few meters. Our
smartphones can do this with the help of a chip that communicates with a group of satellites
in the sky, collectively known as a Global Navigation Satellite System (GNSS). A GNSS is a
group, or constellation, of 24, or more, satellites working together to provide positioning and
timing services globally under any weather conditions.
While the Global Positioning System (GPS) from the United States is the most widely GNSS
used in the world, other GNSS are also available, including:
Typically, "GPS" is used as a general term for the overall equipment and process of using a
GNSS to estimate position.
GPS receivers communicate using several "languages" or protocols, including standard and
non-standard (i.e., proprietary) message formats. In these messaging protocols, information
can be transmitted as binary data (i.e., 1's and 0's) or using the ASCII character encoding.
From all message standards that are use with GPS receivers, the NMEA 0183 is the most
widely used messaging standard.
NMEA is the acronym for the National Marine Electronics Association. This association was
founded in 1957 by electronics dealers to enhance the technology and safety of marine
electronics through installer training and interface standards. The NMEA 0183 messaging
protocol was adopted in 1983 originally for interfacing marine electronics, but its use has
expanded to terrestrial electronics also [3].
The NMEA 0183 is a simple messaging protocol where data in this messaging protocol is
transmitted in ASCII strings or "sentences" from one "talker" to multiple "listeners" at a time.
Another characteristic of the NMEA 0183 messaging protocol is that it uses the RS-422
electrical standard, although it is also compatible with the RS-232 electrical standard [3]. The
serial configuration of the NMEA 0183 messaging protocol is the following:
Sometimes, the NMEA 0183 messaging protocol is confusing because there is not just one
"sentence"; there are different NMEA 0183 sentences with different capabilities and
purposes, usually what changes in the sentence sis the information they can provide. To
understand the different capabilities, purposes and provided information of these sentences,
let us first understand their structure.
COPY
$aaaaa,df1,df2,df3*hh<CR><LF>
A 5 character address field always follow the $ sign while hh is a two hexadecimal
checksum. A NMEA 0183 sentence can have a maximum of 80 characters plus a carriage
return and a line feed. Let us examine now a GPS NMEA 0183 example sentence:
COPY
$GPGGA,181908.00,3404.7041778,N,07044.3966270,W,4,13,1.00,495.144,M,29.200,M,0.1
0,0000,*40
In this NMEA 0183 sentence we can identify the following information:
Sentence Description
$GPGGA Time, position, fix type data.
$GPGLL Latitude, longitude, UTC time of position fix and status.
$GPGSA GPS receiver operating mode, satellites used in the position solution, DOP
values.
$GPGSV Number of satellites in view, satellite ID numbers, elevation, azimuth, SNR
values.
$GPRMC Time, date, position, course, speed data.
$GPVTG Course, speed information relative to the ground.
Notice that $GPRMC sentence is essential as it provides the recommended minimum
navigation data to be provided by a GNSS receiver.
To extract information related to the position, record at least one of the following NMEA
0183 sentences: $GPGGA, $GPGLL, $GPRMC.
The MKR GPS Shield is based on the u-blox SAM-M8Q GNSS module; this module utilizes
concurrent reception of up to three GNSS (GPS, Galileo and GLONASS) and supports both
SBAS and QZSS. It also recognizes multiple constellations simultaneously and provides
outstanding positioning accuracy in scenarios where urban canyon or weak signals are
involved.
The MKR GPS Shield is meant to be used on top of the MKR family boards, but it is also
possible to hook it up to other SAM D-based Arduino boards via its UART or I2C pins (the
Arduino_MKR GPS library can work with both communications protocols).
For more information, you can visit the official documentation of the shield here.
By 2025, there will be more than 25 billion IoT devices connected to the Internet.
Many of the existing IoT devices will be connected to the Internet using short-range wireless
networks such as Wi-Fi®, Bluetooth®, ZigBee, Z-Wave®, etc. Cellular connections using
networks such as 2G, 3G, and 4G will also connect IoT devices to the Internet. Still, these
short and medium-range wireless networks are not always suitable for IoT devices since they
were developed for applications where power consumption and battery life are not significant
issues. IoT devices usually have low-power consumption and send and receive low amounts
of data.
Some of the important use cases for LPWAN's include the following applications:
Several LPWAN technologies use licensed or unlicensed frequencies and proprietary or open
specifications. LoRa® and its Media Access Control (MAC) layer protocol implementation,
LoRaWAN®, is currently one of the existing LPWAN gaining the most traction to support
IoT devices and services.
Bandwidth vs. range of short distance, cellullar and LPWA networks. Image credits: The
Things Network.
Bandwidth vs. range of short distance, cellullar and LPWA networks. Image credits: The
Things Network.
LoRa® modulation technique was invented in 2010 by the French startup Cycleo; then, it
was acquired in 2012 by Semtech.
Based on LoRa®, the LoRaWAN® (LoRa for Wide Area Networks) specification extended
the LoRa® physical communication layer into the Internet by adding a MAC layer. The
LoRaWAN® specification is a software layer that defines how devices must use the LoRa,
for example, when they transmit or receive messages. The LoRaWAN specification is open-
source; it has been supported and maintained by the LoRa Alliance® since 2015.
The LoRa Alliance® is an open, nonprofit organization that collaborates and shares
experiences to promote and drive the success of the LoRaWAN® standard as the leading
open global standard for secure IoT LPWAN connectivity.
Typical LoRaWAN® network architecture example. Image credits: The Things Network.
Typical LoRaWAN® network architecture example. Image credits: The Things Network.
From the image above, notice there is a fundamental difference between a network server and
a gateway. The network server controls the virtualized MAC layer of the LoRaWAN®
network while gateways are devices pre-integrated with the network server to ease the
LPWAN rollout and provisioning. LoRaWAN® network servers and gateways access can be
public or private.
LoRaWAN® networks are usually deployed in a star-of-stars topology; this means that
gateways manage data between end-devices and a network server. Gateways are connected to
the central network server via the Internet, while end-devices use LoRa® to send and receive
data to and from the gateways; end-devices are not exclusively tied to a single gateway, end-
devices broadcast information to all the gateways in range. Communication in LoRaWAN®
networks is natively bi-directional, although uplink communication between end-devices and
the central network server is expected to be predominant in the network.
Star networks topologies provide the best relationship between long-range communications,
the number of gateways or base stations in the network, end-devices power consumption, and
battery life.
Gateways can be added to the network anywhere and anytime without prior planning.
Message delivery is more robust since multiple gateways receive the same data packets
during each uplink.
Data Rates
Communication between end-devices and gateways in LoRaWAN® networks is spread out
on different frequency channels and data rates (communications using different data rates do
not interfere with each other).
LoRa® supports data rates ranging from 300 bps to 5 kbps for a 125 kHz bandwidth.
To maximize the battery life of each end-device and the overall capacity available through
the network, LoRaWAN® uses an Adaptive Data Rate (ADR) mechanism for optimizing
data rates, airtime, and power consumption. ADR controls the following transmission
parameters on end-devices:
Spreading factor: the speed of data transmission. Lower spreading factors mean a higher data
transmission rate.
Bandwidth: the amount of data that can be transmitted from one point to another within the
network.
Transmission power: the energy that the end-device transmitter produces at its output.
The table below shows compares spreading factor, data rate, and time on-air at a bandwidth
of 125 kHz (range is an indicative value, it will depend on the propagation conditions):
In the EU868 band, the end-device must respect the maximum transmit duty cycle relative to
the sub-band used and local regulations (1% for end-devices).
In the US915 band, the end-device must respect the maximum transmit duration (or dwell
time) relative to the sub-band used and local regulations (400ms).
LoRaWAN® network layers. Image credits: Semtech.
LoRaWAN® network layers. Image credits: Semtech.
Regional Parameters
The LoRaWAN® Regional Parameters specification is a companion to the LoRaWAN®
network layer specification. While the LoRaWAN® network layer specification defines the
air interface between a compliant end-device (sensor, actuator, tracker, etc.) and a compliant
network core, the LoRaWAN® Regional Parameters specification defines the adaptation of
the LoRaWAN® network layer specification to comply with the various regulations enforced
throughout the world on the use of various frequency bands of the unlicensed spectrum which
are available.
Also, the LoRaWAN® Regional Parameters specification documents the physical layer
configurations required for the compliant operation of LoRaWAN® Link Layer radios using
various radio frequency modulation techniques.
The idea behind the LoRaWAN® Regional Parameters specification is to create the smallest
number of regional channel plans covering the largest possible number of regulatory regions.
With this, complexity is decreased to implementers as well as the certification cost (end-
device certification is enumerated by network layer, Regional Parameters and channel plan
revision).
LoRaWAN® Regional Parameters specifications do not specify everything. They only cover
a region by specifying the common denominator. For example, the LoRaWAN® Regional
Parameters for Asia only specify a common subset of channels, but there are variations
between regulations in Asian countries. Furthermore, each network server, for example TTN,
is free to select additional parameters, such as additional emission channels.
For more information, you can read the RP002-1.0.2 LoRaWAN® Regional Parameters
document here, we also have a more detailed tutorial about LoRaWAN® Regional
Parameters and Arduino hardware; the tutorial can be found here here
Classes
The LoRaWAN® specification has three different communication profiles between devices
and applications: Class A, Class B, and Class C. Each class serves different application needs
and has optimized requirements for specific purposes. The main difference between the three
classes is latency and power consumption; end-devices can always send uplinks when
needed, but its class will determine when to receive downlinks.
All LoRaWAN devices must implement Class A; Class B, and Class C are extensions of
Class A profile.
Class A: "Aloha"
Class A devices implement a bi-directional communication profile where two short
downlinks follow the end-device uplink transmission receive windows, usually referred to as
RX1 and RX2. If the server does not respond in either RX1 or RX2 windows, the next
opportunity will be after the next uplink transmission. Class A devices are often battery-
powered and spend most of the time in sleep mode; therefore, they have the lowest energy
consumption, keep long intervals between uplinks, and have high downlink latency.
NwkSKey and AppSKey are AES-128 root keys specific to the end-device, end-devices
manufacturers, or application owners assigned them.
Over-The-Air Activation (OTAA): In this method, end-devices are not initialized for any
particular network; they send a JOIN request to a specific LoRaWAN® network and then
receive a device address and an authorization token from which session keys are derived;
NwkSKey and AppSKey are derived during this procedure from a root AppKey pre-
provisioned in the end-devices by its manufacturer.
Over-The-Air activation process. Image credits: Heath Raftery.
Over-The-Air activation process. Image credits: Heath Raftery.
PRO hardware also has LoRa® connectivity. The Arduino® Portenta H7 board can have
LoRa® connectivity with the Portenta Vision Shield - LoRa; this addon board also features a
CMCMWX1ZZABZ module from Murata® for LoRa® connectivity, the same module
present in the MKR 1300 and 1310 boards.
The Arduino® Edge Control, a remote monitoring and control solution optimized for outdoor
environments, can expand its wireless connectivity capabilities by adding an MKR WAN
1300 or 1310 board. Edge Control can be positioned anywhere and is well suited for smart
agriculture and other applications that require intelligent control in remote locations.
You can use both libraries in the Arduino IDE, online and offline. If you are using the online
IDE, you don't need to do anything, both libraries are already installed and ready to be used.
If you are using the offline IDE, you must install the libraries manually. Installing libraries
can be done easily by navigating to Tools > Manage Libraries... and then look for MKRWAN
library by Arduino and LoRa by Sandeep Mistry; remember to install the latest version of the
libraries.
Currently, there are two versions of the MKRWAN library; MKRWAN_v2 library is still in
beta.
Before sending and receiving messages from TTN, you need to register your board first to the
network. For this, you need to know your board's Device EUI. You can get your board's
Device EUI by running the FirstConfiguration example from the MKRWAN library. With
your Device EUI, you can register your board in TTN by creating an account in TTN, adding
an application, and registering your board. This tutorial from TTN explains the process.
Once your device is registered on TTN, you can start sending and receiving data with the
following code:
COPY
Explain
/*
Send and receive data from a LoRaWAN network
This sketch demonstrates how to send and receive data with the MKR WAN 1300/1310
board.
This example code is in the public domain.
*/
#include <MKRWAN.h>
#include "arduino_secrets.h"
LoRaModem modem;
void setup() {
// Serial port initialization
Serial.begin(115200);
while (!Serial);
// With the module initialized, we can get its version and device EUI
// Your network server provider requires device EUI information to connect to their network
Serial.print("- Your module version is: ");
Serial.println(modem.version());
Serial.print("- Your device EUI is: ");
Serial.println(modem.deviceEUI());
// NOTE: independent of this setting, the modem will not allow sending more than one
message every 2 minutes
// This is enforced by firmware and can not be changed.
}
void loop() {
Serial.println();
Serial.println("- Enter a message to send to network");
Serial.println("(make sure that end-of-line 'NL' is enabled)");
The LoRa Alliance® Resource Hub. Here you can access LoRaWAN® technical documents
and Whitepapers from The LoRa Alliance®.
LoRa Developer Portal from Semtech. Here you can find technical papers and user guides as
well as specifications and datasheets from Semtech.
The Things Network documentation. Here you can learn all about LoRaWAN® and The
Things Network!
The Things Academy online course in Udemy. A free online course where you'll learn all
about LoRa® and LoRaWAN®, and get ready to start building your own Low Power Wide
Area Network applications.
1-Wire Protocol
Learn about the communication between devices or sensors using the OneWire protocol.
AuthorArduino Community
Last revision08/02/2024
This article was revised on 2022/09/28 by Hannes Siebeneicher.
1-Wire communication is a protocol operating through one wire between the controller
device and the peripheral device. This article covers the basics of using the 1-Wire protocol
with an Arduino with the help of the OneWire library. The following sections provide
information about the 1-Wire protocol, interface, power, addressing devices, reading devices
and finally a short glimpse into the library's history.
Latest version
The latest version of the library is on Paul Stoffregen's site.
On a 1-Wire network, which Dallas has dubbed a "MicroLan" (trademark), a single controller
device communicates with one or more 1-Wire peripheral devices over a single data line,
which can also be used to provide power to the peripheral devices. (Devices drawing power
from the 1-wire bus are said to be operating in parasitic power mode.) Tom Boyd's guide to
1-Wire may tell you more than you want to know, but it may also answer questions and
inspire interest.
The 1-Wire temperature sensors have become particularly popular, because they're
inexpensive and easy to use, providing calibrated digital temperature readings directly. They
are more tolerant of long wires between sensor and Arduino. The sample code below
demonstrates how to interface with a 1-Wire device using Jim Studt's OneWire Arduino
library, with the DS18S20 digital thermometer as an example. Many 1-Wire chips can
operate in both parasitic and normal power modes.
1-Wire Interfaces
Dedicated Bus Controller
Dallas/Maxim and a number of other companies manufacture dedicated bus controller for
read/write and management of 1-Wire networks. Most of these are listed here:
http://owfs.org/index.php?page=bus-masters
These devices are specifically designed and optimized to read and write efficiently to 1-Wire
devices and networks. Similar to UART/USART controller, they handle clocked operations
natively with the use of a buffer, offloading the processing load from the host processor (e.g.,
sensor gateway or microcontroller) thereby increasing accuracy . External pull-up resistors
are also often not required.
Many of the chips provide error-handling that specifically deals with loss of signal integrity,
level variation, reflections, and other bus issues that may cause problems, particularly on
large networks. Many of the devices have additional features, and are offered on a large
variety of interfaces. They range in price from $1 to $30.
Another key advantage is support of , a read/write file system with vast device support for 1-
Wire controller that exposes many native functions for a wide variety of 1-Wire device types.
UART/USART controller
Most UART/USARTs are perfectly capable of sustained speeds well in excess of the
15.4kbps required of the 1-Wire bus in standard mode. More important, the clock and
buffering is handled separately, again offloading it from the main process of the
microcontroller or main processor. This implementation is discussed here:
http://www.maximintegrated.com/en/app-notes/index.mvp/id/214.
Bitbanging approaches
Where native buffering/clock management is not available, 1-Wire may be implemented on a
general purpose IO (GPIO) pin, where manual toggle of the pin state is used to emulate a
UART/USART with reconstruction of the signal from the received data. These are typically
much less processor-efficient, and directly impact and are directly impacted by other
processes on the processor shared with other system processes.
On Arduino and other compatible chips, this may be done with the OneWire library.
On single-board computers such as the Raspberry Pi, 1-Wire network read is often possible
using kernel drivers that offer native support. The w1-gpio, w1-gpio-therm, and w1-gpio-
custom kernel mods are included in the most recent distributions of Raspbian and are quite
popular, as they allow interfacing with a subset of 1-Wire device with no additional
hardware. Currently, however, they have limited device support, and have bus size limitations
in software.
This current is usually very small, but may go as high as 1.5 mA when doing a temperature
conversion or writing EEPROM. When a peripheral device is performing one of these
operations, the bus controller must keep the bus pulled high to provide power until the
operation is complete; a delay of 750ms is required for a DS18S20 temperature conversion.
The controller can't do anything during this time, like issuing commands to other devices, or
polling for the peripheral's operation to be completed. To support this, the OneWire library
makes it possible to have the bus held high after the data is written.
Note on resistors:
For larger networks, you can try smaller resistors.
The ATmega328/168 datasheet indicates starting at 1k6 and a number of users have found
smaller to work better on larger networks.
Single-device commands
Before sending a command to a single peripheral device, the controller must first select that
device using its unique ROM. Subsequent commands will be responded to by the selected
device, if found.
Multiple-device commands
Alternatively, you can address a command to all peripheral devices by issuing a 'Skip ROM'
command (0xCC), instead. It is important to consider the effects of issuing a command to
multiple devices. Sometimes, this may be intended and beneficial. For example, issuing a
Skip ROM followed by a convert T (0x44) would instruct all networked devices that have a
Convert T command to perform a temperature conversion. This can be a time-saving and
efficient way of performing the operations. On the other hand, issuing a Read Scratchpad
(0xBE) command would cause all devices to report Scratchpad data simultaneously. Power
consumption of all devices (for example, during a temperature conversion) is also important
when using a Skip ROM command sequence.
Read Scratchpad
Once the data has been converted, it is copied into the Scratchpad memory, where it may be
read. Note that the Scratchpad may be read at any time without a conversion command to
recall the most previous reading, as well as the resolution of the device and other device-
dependent configuration options.
History
In 2007, Jim Studt created the original OneWire library that makes it easy to work with 1-
Wire devices. Jim's original version only worked with arduino-007 and required a large (256-
byte) lookup table to perform CRC calculations. This was later updated to work with arduino-
0008 and later releases. The most recent version eliminates the CRC lookup table and has
been tested under arduino-0010.
The OneWire library had a bug causing an infinite loop when using the search function but
Version 2.0 merges Robin James's improved search function and includes Paul Stoffregen's
improved I/O routines (fixes occasional communication errors), and also has several small
optimizations.
Version 2.1 added compatibility with Arduino 1.0-beta and an improved temperature
example (Paul Stoffregen), DS250x PROM example (Guillermo Lovato), chipKit
compatibility (Jason Dangel), CRC16, convenience functions and DS2408 example (Glenn
Trewitt).
Miles Burton derived its Dallas Temperature Control Library from it as well.
Example code
COPY
Explain
#include <OneWire.h>
void setup(void) {
// initialize inputs/outputs
Serial.begin(9600);
}
void loop(void) {
byte i;
byte present = 0;
byte data[12];
byte addr[8];
ds.reset_search();
if ( !ds.search(addr)) {
ds.reset_search();
return;
Serial.print("R=");
Serial.print(addr[i], HEX);
Serial.print(" ");
return;
if ( addr[0] == 0x10) {
else {
Serial.println(addr[0],HEX);
return;
ds.reset();
ds.select(addr);
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(addr);
Serial.print("P=");
Serial.print(present,HEX);
Serial.print(" ");
data[i] = ds.read();
Serial.print(data[i], HEX);
Serial.print(" ");
Serial.print(" CRC=");
}
Converting HEX to something meaningful (Temperature)
In order to convert the HEX code to a temperature value, first you need to identify if you are
using a DS18S20, or DS18B20 series sensor. The code to read the temperature needs to be
slightly different for the DS18B20 (and DS1822), because it returns a 12-bit temperature
value (0.0625 deg precision), while the DS18S20 and DS1820 return 9-bit values (0.5 deg
precision).
First, you need to define some variables, (put right under loop() above)
COPY
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
Then for a DS18B20 series you will need to add the following code below the
Serial.println();
COPY
Explain
LowByte = data[0];
HighByte = data[1];
if (SignBit) // negative
Whole = Tc_100 / 100; // separate off the whole and fractional portions
Serial.print("-");
Serial.print(Whole);
Serial.print(".");
if (Fract < 10)
Serial.print("0");
Serial.print(Fract);
Serial.print("\n");
This block of code converts the temperature to deg C and prints it to the Serial output.
COPY
Explain
#include <OneWire.h>
#include <LiquidCrystal.h>
// LCD=======================================================
#define LCD_WIDTH 20
#define LCD_HEIGHT 2
#define MAX_DS1820_SENSORS 2
byte addr[MAX_DS1820_SENSORS][8];
void setup(void)
{
lcd.begin(LCD_WIDTH, LCD_HEIGHT,1);
lcd.setCursor(0,0);
lcd.print("DS1820 Test");
if (!ds.search(addr[0]))
lcd.setCursor(0,0);
ds.reset_search();
delay(250);
return;
if ( !ds.search(addr[1]))
lcd.setCursor(0,0);
ds.reset_search();
delay(250);
return;
char buf[20];
void loop(void)
byte i, sensor;
byte present = 0;
byte data[12];
for (sensor=0;sensor<MAX_DS1820_SENSORS;sensor++)
{
lcd.setCursor(0,0);
return;
if ( addr[sensor][0] != 0x10)
lcd.setCursor(0,0);
return;
ds.reset();
ds.select(addr[sensor]);
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(addr[sensor]);
{ // we need 9 bytes
data[i] = ds.read();
}
LowByte = data[0];
HighByte = data[1];
if (SignBit) // negative
Tc_100 = (TReading*100/2);
Whole = Tc_100 / 100; // separate off the whole and fractional portions
lcd.setCursor(0,sensor%LCD_HEIGHT);
lcd.print(buf);
AuthorHannes Siebeneicher
Last revision08/02/2024
Introduction
This article contains information about the Modbus serial communication protocol and how it
can be used with Arduino hardware. The different elements are highlighted, compatible
libraries and boards are shown together with example code. The following section gives an
overview of Modbus compatible Arduino boards and the libraries to enable Modbus protocol
capability. Depending on the hardware you are using, the libraries might vary. Therefore, it is
always important to check your device specifications.
What is Modbus?
Modbus is an open serial communication protocol used for transmitting information over
serial lines between electronic devices. It was originally published by Modicon (now
Schneider Electric) in 1979. The Modbus protocol is the oldest and by far the most popular
automation protocol in the field of automation process. It enables devices, such as energy
meters or humidity sensors connected to the same network to communicate the results to a
supervisory computer or a Programmable Logic Controller (PLC).
Several versions of the Modbus protocol exist such as Modbus RTU, Modbus ASCII,
Modbus TCP and Modbus Plus. It is based on a controller-peripheral (formerly known as
master-slave) architecture and communication between nodes is achieved with send request
and read response type messages. Modbus communicates over several types of physical
media such as RS-232/RS-485 or Ethernet. The original Modbus interface ran on RS-232
serial communication but most of the later Modbus implementations use RS-485 because it
allows for longer distances, higher speeds and the possibility of multiple devices on a single
multi-drop network. The communication over serial RS-485 physical media works with two-
wire transmit and receive connections.
On interfaces like RS-485 and RS-232, the Modbus messages are sent in plain form over the
network and the network will be dedicated to only Modbus communication. However, if your
network requires multiple heterogeneous devices, TCP/IP over Ethernet based protocol is
recommended. In this case, Modbus and other types of mixed protocols can co-exist in the
same physical interface simultaneously. The main Modbus message structure is peer-to-peer,
but it can also function on point-to-point and multidrop networks. As mentioned, the Modbus
protocol communicates using a controller-peripheral technique in which only one device can
initiate transactions, called queries.
ModbusProtocol
Device Address
Every peripheral device has its own address which it responds to when addressed by the
controller. All other devices ignore the message if the address doesn't match their own.
Device addresses are assigned in the range of 1 - 247, but without additional hardware, the
stable number of devices should be limited to 32 as it could cause instability when attaching
more devices.
Function Code
The Function code tells the peripheral device if it should read or write data from the internal
memory registers. Many of the data types are named from their use in driving relays, for
example, a single-bit physical output is called a coil, and a single-bit physical input is called a
discrete input or a contact. The following functions are supported by the Modbus poll:
01 READ COIL
02 READ DISCRETE INPUT
03 READ HOLDING REGISTERS
04 READ INPUT REGISTERS
05 WRITE SINGLE COIL
06 WRITE SINGLE REGISTER
15 WRITE MULTIPLE COILS
16 WRITE MULTIPLE REGISTERS
Data
The data field contains the requested or send data. In the request form used by the Arduino
Modbus library, the data field requires you to enter the starting registers and register count.
Two bytes are added to the end of every Modbus message for error detection and every byte
in the message is used to calculate the CRC. The receiving device then also calculates the
CRC and compares it to the CRC from the sending device. If even one bit in the message is
received incorrectly, the CRC’s will be different resulting in an error.
A lot of Arduino boards are Modbus compatible especially if you consider Ethernet-type
messages. In case you want to communicate via RS-485, MKR 485 Shields will help you
convert any MKR board into a Modbus compatible device. Check out this tutorial to learn
more about sending data between two MKR 485 Shields.
When using the Modbus library, sending messages is fairly straightforward as you can see in
the exemplary request format function below:
You have to check the device-specific documentation to attain the correct address, function
code, starting registers and register count. The CRC error check is taken care of by the
Modbus library. The example below shows how to implement the Modbus library in order to
make it easy to understand.
Example
Let's say you have a Modbus-compatible energy meter working with RS-485. In our case, we
use a model from Finder which uses Modbus RTU protocol but you can use a different one.
finderRequestFrame
The function code states 04 which we know that it stands for INPUT REGISTERS (see
Function Code).
The starting register is 00 6B (or 0x6B) but for the Modbus library to work we need to
change it from hexadecimal to decimal. In this example, the starting register is stated as
hexadecimal but if we scroll down further in the documentation we can see that the registers
are written in decimals:
modbusMeasurmentFinder
We can also see that the register for U1 takes up 30107 and 30108 which means that the
register count is 2.
Using the readVoltage() function, defined within the parameters we discussed, will retrieve
the voltage measurement from the Finder energy meter.
Inside you can see how the requestFrom() is being called with Device Address, Function
Code, Starting Register and Register Count as parameters.
COPY
Explain
/**
Dedicated to read voltage information from a Modbus-compatible Finder 7M.24 energy
meter.
@param none
@return Returns obtained voltage information.
*/
float readVoltage() {
float volt = 0.;
// Send reading request over RS485
if (!ModbusRTUClient.requestFrom(0x21, INPUT_REGISTERS, 30107, 2)) {
// Error handling
Serial.print("- Failed to read the voltage! ");
Serial.println(ModbusRTUClient.lastError());
} else {
// Response handler
uint16_t word1 = ModbusRTUClient.read(); // Read word1 from buffer
uint16_t word2 = ModbusRTUClient.read(); // Read word2 from buffer
uint32_t millivolt = word1 << 16 | word2; // Join word1 and word2 to retrieve voltage
value in millivolts
volt = millivolt/1000.0; // Convert to volts
}
return volt;
}
As this article does not cover all types of Modbus implementation, focusing on Modbus RTU,
the following section includes more references to read about the different implementation
types in more detail.
AuthorHannes Siebeneicher
Last revision08/02/2024
Introduction
Bluetooth® Low Energy®, often referred to as Bluetooth® LE, is a wireless communication
technology designed for short-range data exchange between electronic devices. It emerged as
a response to the need for energy-efficient wireless communication in various applications,
especially those where power consumption is a critical concern.
Unlike its predecessor, Bluetooth® Classic, which is optimized for continuous and relatively
high-data-rate communication, Bluetooth® LE focuses on minimizing energy consumption
while maintaining connectivity. This makes Bluetooth® LE particularly suitable for
applications that require long battery life, such as fitness trackers, healthcare devices, smart
sensors, and Internet of Things (IoT) devices.
The aim of this article is to highlight the basic concepts of Bluetooth® Low Energy and
explain how to use the ArduinoBLE library to create Bluetooth® LE projects with
compatible Arduino boards.
Similar to how Harald Blåtand united Denmark the Bluetooth® protocol was meant to unite
various devices and communication protocols.
Technical Specifications
The following parts explore the core concepts and technical specifications of Bluetooth®
Low Energy.
Range
The range of a Bluetooth® Low Energy connection can vary depending on several factors,
but in typical scenarios, it can extend up to approximately 50 meters (or roughly 164 feet) in
a line-of-sight environment. This range can be affected by several factors:
Obstacles: Physical obstacles such as walls, furniture, and other objects can significantly
reduce the range of a Bluetooth® LE connection. Thick walls and materials like concrete can
be particularly challenging for Bluetooth® LE signals to penetrate.
Interference: As mentioned earlier, the 2.4 GHz band is shared by various wireless devices.
Interference from other devices operating in the same frequency range can impact the range
and reliability of Bluetooth® LE connections.
Antenna Design: The design and quality of the antennas in both the central and peripheral
devices can influence the range. Devices with well-designed antennas tend to have better
coverage.
Orientation: The relative orientation of the central and peripheral devices also affects range.
A clear line of sight between devices typically results in the best range, while obstructed lines
of sight can reduce it.
Central Device: A central device in Bluetooth® LE is typically a more capable device with
features like a higher CPU power, more memory, or a larger battery. Central devices take on
the role of initiating connections to peripheral devices. For example, your smartphone is often
a central device when connecting to Bluetooth® LE peripherals like fitness trackers, smart
sensors or an Arduino board.
Bluetooth® LE Roles
Bluetooth® LE Roles
Bluetooth® LE Modes
Services
In Bluetooth® LE, a service can be thought of as a logical grouping of related data
measurements or functionalities provided by a peripheral device. These data measurements
can represent various aspects of the device's capabilities or the information it collects.
For example, consider a weather monitoring sensor. It might have a service called "Weather
Data" that encompasses measurements like temperature, humidity, and wind speed. Another
service, "Energy Information," could include data related to battery level and energy
consumption.
Characteristics
Within each service, we have characteristics. Characteristics are individual data points or
attributes that provide specific information or measurements.
For instance, in the "Weather Data" service mentioned earlier, characteristics may include
"Temperature," "Humidity," and "Wind Speed." These characteristics continuously record
data and update as new measurements become available.
Similarly, the "Energy Information" service may consist of characteristics like "Battery
Level" and "Energy Consumption."
A UUID is a 128-bit value that serves as a universally unique name for a service or
characteristic. It acts like a label or identifier that central devices use to identify and
communicate with specific services and characteristics.
UUIDs play a crucial role in Bluetooth® LE communication because they ensure that central
devices can accurately locate and interact with the desired data points on peripheral devices.
They eliminate ambiguity and allow for precise data retrieval and control.
As you explore Bluetooth® LE further, you'll encounter various predefined services and
characteristics used in common applications. These standardized profiles simplify the
development process, making it easier to create Bluetooth® LE-based projects and
applications.
Profiles
Bluetooth® LE profiles are predefined sets of services and characteristics that standardize
how Bluetooth® LE devices interact with each other. These profiles define the behavior and
capabilities of Bluetooth® LE devices, making it easier for different devices to communicate
seamlessly. Let's delve into Bluetooth® LE profiles:
Each profile is tailored to a specific use case or application, ensuring that devices of different
manufacturers can work together seamlessly when using the same profile.
Battery Service: The Battery Service provides information about the battery level of a device.
It typically includes a Battery Level characteristic that central devices can read to monitor the
battery status of a peripheral device, such as a wireless headset or smartwatch.
Heart Rate Service: The Heart Rate Service is commonly used in fitness and health
monitoring applications. It includes characteristics that provide real-time heart rate data,
allowing central devices like smartphones or fitness trackers to monitor a user's heart rate
during exercise.
Generic Access Profile (GAP): While not a service in itself, GAP defines the roles and
procedures for device discovery and connection establishment in Bluetooth® LE. It plays a
vital role in enabling devices to find and connect to each other seamlessly.
In addition to standard profiles, developers have the flexibility to create custom profiles
tailored to their specific application needs. These custom profiles define unique services and
characteristics that match the requirements of a particular project.
Using Bluetooth® LE profiles, developers can leverage standardized profiles for common
applications or create custom profiles for specialized projects. This standardized approach
simplifies the development process, enhances interoperability, and allows for the creation of
diverse Bluetooth® LE-based applications, from health monitoring to home automation.
As you explore Bluetooth® LE further, you'll discover a wide range of profiles designed to
support various use cases. These profiles play a crucial role in ensuring that Bluetooth® LE
devices can seamlessly communicate and provide valuable data to central devices.
Bluetooth® Classic
Bluetooth® Low Energy is distinctly different from Bluetooth® Classic. Bluetooth® Classic
operates in a manner similar to a serial port or UART (Universal Asynchronous Receiver-
Transmitter), which is commonly used for point-to-point communication.
Power Consumption
Bluetooth® Classic: offers higher data transfer rates suitable for tasks like streaming audio or
transferring files between devices.
Bluetooth® Low Energy: sacrifices data transfer speed in favor of energy efficiency. It's ideal
for applications that require intermittent or small bursts of data, such as sending sensor
readings or control commands.
Connection Types
Bluetooth® Low Energy: supports two primary modes - advertising and connection. In
advertising mode, a BLE peripheral periodically broadcasts its presence but doesn't maintain
a continuous connection, conserving power. When needed, a central device can establish a
connection for data exchange.
ArduinoBLE Library
The ArduinoBLE Library is the main library enabling Bluetooth® Low Energy on
compatible Arduino boards. You must first download and install the ArduinoBLE library. See
our instructions on how to install a library.
In the following section you will find an overview and explanation of the library's most
important methods and how to use them:
These are just a few of the most important methods. You can find more information about all
methods and their details in the ArduinoBLE reference.
Examples
Below you can find examples showing how to send data between two Arduino boards and
how to connect to your Arduino board, reading and writing values using your smartphone.
Central
This example scans for Bluetooth® Low Energy peripherals with a specific UUID (in this
case another Arduino board), connects to it, and lets you control the built-in LED with a
button connected to pin 4.
The circuit below uses an Arduino UNO R4 WiFi. You can use any of the Bluetooth®
compatible boards from this list.
central
central
COPY
Explain
#include <ArduinoBLE.h>
void setup() {
Serial.begin(9600);
while (!Serial);
// configure the button pin as input
pinMode(buttonPin, INPUT);
void loop() {
// check if a peripheral has been discovered
BLEDevice peripheral = BLE.available();
if (peripheral) {
// discovered a peripheral, print out address, local name, and advertised service
Serial.print("Found ");
Serial.print(peripheral.address());
Serial.print(" '");
Serial.print(peripheral.localName());
Serial.print("' ");
Serial.print(peripheral.advertisedServiceUuid());
Serial.println();
if (peripheral.localName() != "LED") {
return;
}
// stop scanning
BLE.stopScan();
controlLed(peripheral);
if (peripheral.connect()) {
Serial.println("Connected");
} else {
Serial.println("Failed to connect!");
return;
}
if (!ledCharacteristic) {
Serial.println("Peripheral does not have LED characteristic!");
peripheral.disconnect();
return;
} else if (!ledCharacteristic.canWrite()) {
Serial.println("Peripheral does not have a writable LED characteristic!");
peripheral.disconnect();
return;
}
while (peripheral.connected()) {
// while the peripheral is connected
if (oldButtonState != buttonState) {
// button changed
oldButtonState = buttonState;
if (buttonState) {
Serial.println("button pressed");
Serial.println("Peripheral disconnected");
}
Peripheral
This example is the corresponding sketch to the one above, setting up your Arduino board as
peripheral with the correct UUID, advertising a built-in LED characteristic. Since we are only
using the built-in LED you don't need to wire any components.
// Bluetooth® Low Energy LED Switch Characteristic - custom 128-bit UUID, read and
writable by central
BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214",
BLERead | BLEWrite);
void setup() {
Serial.begin(9600);
while (!Serial);
// begin initialization
if (!BLE.begin()) {
Serial.println("starting Bluetooth® Low Energy module failed!");
while (1);
}
// add service
BLE.addService(ledService);
// start advertising
BLE.advertise();
void loop() {
// listen for Bluetooth® Low Energy peripherals to connect:
BLEDevice central = BLE.central();
COPY
Explain
#include <ArduinoBLE.h>
BLEService newService("180A"); // creating the service
void setup() {
Serial.begin(9600); // initialize serial communication
while (!Serial); //starts the program if we open the serial monitor.
BLE.setLocalName("<My Board Name>"); //Setting a name that will appear when scanning
for Bluetooth® devices
BLE.setAdvertisedService(newService);
void loop() {
if (currentMillis - previousMillis >= 200) { // if 200ms have passed, we check the battery
level
previousMillis = currentMillis;
if (switchChar.written()) {
if (switchChar.value()) { // any value other than 0
Serial.println("LED on");
digitalWrite(ledPin, HIGH); // will turn the LED on
} else { // a 0 value
Serial.println(F("LED off"));
digitalWrite(ledPin, LOW); // will turn the LED off
}
}
}
}
digitalWrite(LED_BUILTIN, LOW); // when the central disconnects, turn off the LED
Serial.print("Disconnected from central: ");
Serial.println(central.address());
}
}
Step 2 - Run LightBlue App
Control your board using LightBlue
Control your board using LightBlue
Summary
In this article we explored the basics of Bluetooth® Low Energy and how to set it up on your
Arduino board using the ArduinoBLE library. We highlighted the differences between
Bluetooth® LE and Bluetooth® Classic and finally we provided examples showcasing how
you can send data between two Arduino boards or how to use your smartphone to connect to
your Arduino board.
AuthorHannes Siebeneicher
Last revision08/02/2024
In this article, you will learn the basics of Universal Asynchronous Receiver-Transmitter
(UART), a serial communication protocol that can be used to send data between an Arduino
board and other devices. This is the protocol used when you send data from an Arduino to
your computer, using the classic Serial.print() method.
UART is one of the most used device-to-device (serial) communication protocols. It’s the
protocol used by Arduino boards to communicate with the computer. It allows an
asynchronous serial communication in which the data format and transmission speed are
configurable. It's among the earliest serial protocols and even though it has in many places
been replaced by SPI and I2C it's still widely used for lower-speed and lower-throughput
applications because it is very simple, low-cost and easy to implement.
Communication via UART is enabled by the Serial class, which has a number of methods
available, including reading & writing data.
If you want to jump straight to the examples click here or go to the end of this article.
Overview
Overview
Serial Class
Arduino UART Pins
Technical Specifications
How UART Works
Timing and Synchronization
UART Messages
Serial USB Examples
Basic Print Example
Read
RX/TX Pin Examples
Transmit / Receive Messages
Control Built-in LED
Serial Class
With the Serial class, you can send / receive data to and from your computer over USB, or to
a device connected via the Arduino's RX/TX pins.
When sending data over USB, we use Serial. This data can be viewed in the Serial Monitor in
the Arduino IDE.
When sending data over RX/TX pins, we use Serial1.
The GIGA R1 WiFi, Mega 2560 and Due boards also have Serial2 and Serial3
The Serial class have several methods with some of the essentials being:
begin() - begins serial communication, with a specified baud rate (many examples use either
9600 or 115200).
print() - prints the content to the Serial Monitor.
println() - prints the content to the Serial Monitor, and adds a new line.
available() - checks if serial data is available (if you send a command from the Serial
Monitor).
read() - reads data from the serial port.
write() - writes data to the serial port.
For example, to initialize serial communication on both serial ports, we would write it as:
COPY
Serial.begin(9600); //init communication over USB
Serial1.begin(9600); //communication over RX/TX pins
The Serial class is supported on all Arduino boards.
As seen in the image above when using parallel communication an 8-bit message would
require eight cables while serial communication only requires one cable for sending messages
and one for receiving.
Consider that you need to connect a common ground between the devices to define the high
and low signals for UART communication. Without a common ground, devices may not be
able to correctly interpret transmitted data.
Components
The key components of UART include the transmitter, receiver, and baud rate. The
transmitter collects data from a source, formats it into serial bits, and sends it via a TX
(Transmit) pin. The receiver receives it via a RX (Receive) pin, processes incoming serial
data and converts it into parallel data for the host system. The baud rate determines the speed
of data transmission.
Baud Rate
The baud rate is a fundamental parameter in UART communication. It defines the speed at
which data is transmitted over the communication channel. The baud rate is specified in bits
per second (bps) and represents the number of bits transmitted in one second. In UART, both
the transmitting and receiving devices must agree on the same baud rate to ensure successful
communication.
The significance of the baud rate lies in its direct influence on the data transfer speed. A
higher baud rate allows for faster data transmission, but it also demands a more precise
timing synchronization between the sender and receiver. On the other hand, a lower baud rate
may be suitable for applications where timing accuracy is less critical, but it results in slower
data transfer. When programming your Arduino common baud rates are 9600, 115200, 4800,
and 57600. In your code, you set the baud rate like so:
COPY
Serial.begin(9600);
Flow Control in UART
UART Flow Control is a method for slow and fast devices to communicate with each other
over UART without the risk of losing data. Consider the case where two units are
communicating over UART. A transmitter T is sending a long stream of bytes to a receiver R.
R is a slower device than T, and R cannot keep up. It needs to either do some processing on
the data or empty some buffers before it can keep receiving data.
R needs to tell T to stop transmitting for a while. This is where flow control comes in. Flow
control provides extra signaling to inform the transmitter that it should stop (pause) or start
(resume) the transmission.
Several forms of flow control exist. For example, hardware flow control uses extra wires,
where the logic level on these wires define whether the transmitter should keep sending data
or stop. With software flow control, special characters are sent over the normal data lines to
start or stop the transmission.
UART Messages
In UART communication, each data frame is encapsulated by start and stop bits. These bits
serve a vital role in establishing the boundaries of data transmission and ensuring
synchronization between the sender and receiver.
Frame Format
Frame Format
Frame Format
Start Bit
A single start bit is transmitted at the beginning of each UART frame. The primary purpose
of the start bit is to indicate the start of the data transmission and prepare the receiver for data
reception.
The start bit is always logic low (0) for UART communication. This means that the start bit is
transmitted as a voltage level that is lower than the logic high threshold, typically at the
receiver's end.
When the receiver detects a start bit, it knows that a new data frame is beginning, and it
prepares to receive the incoming bits.
Start Bit
Start Bit
Data Bits
Data bits are a fundamental component of UART communication as they carry the actual
information to be transmitted. The number of data bits in a UART frame can vary, but a
common and widely used configuration is 8 bits. However, UART supports various character
sizes, including 7-bit and 6-bit configurations, depending on the specific application
requirements.
Data Bits
Data Bits
Character Size
The character size in UART communication is defined by the number of data bits within a
frame. It's essential to choose the appropriate character size to match the requirements of the
data being transmitted. Here are some common character size configurations:
8-Bit: This is the most prevalent character size in UART communication. It allows for the
transmission of a byte of data, which can represent a wide range of values, including ASCII
characters, numerical values, and more.
7-Bit: In cases where data size needs to be smaller, 7-bit character size is utilized. It's suitable
for applications that require less data overhead and can represent 128 different values.
6-Bit: For even more compact data representation, 6-bit character size can be used. This
configuration provides the ability to represent 64 different values.
Data Encoding
Data bits represent the characters or data using binary encoding, where each bit corresponds
to a power of 2. Each bit within the data byte holds a specific position and weight in the
binary representation. This encoding allows for the transmission of a wide range of
information, making UART versatile for various data types, from simple text characters to
complex binary data.
Data Integrity
Parity
In addition to data bits, UART communication may include a parity bit as part of the data
frame. Parity is an error-checking mechanism that can help detect data transmission errors.
Parity can be set to "odd" or "even," and it ensures that the total number of bits set to logic
"1" in a character is either even or odd, depending on the chosen parity type. The presence of
a parity bit allows the receiver to verify the integrity of the received data. If the number of "1"
bits don't match the expected parity, an error is detected.
Parity Bit
Parity Bit
Stop Bits
One or more stop bits are sent after the data bits within each UART frame. The stop bit(s)
signal the end of the data byte and serve to indicate the conclusion of data transmission. The
most common configuration is to use one stop bit, but in situations where added reliability is
required, two stop bits can be employed.
The polarity of the stop bit(s) can vary, with some systems using a high stop bit and others
using a low stop bit based on the specific UART configuration.
Stop Bits
Stop Bits
COPY
Explain
void setup(){
Serial.begin(9600); //initialize serial communication at a 9600 baud rate
}
void loop(){
Serial.println("Hello world!");
delay(1000);
}
Serial.print() / Serial.println() is used in almost all Arduino sketches, as you can understand
what goes on in the board, and design programs to provide you information on specific
events.
Read
To send data from a computer to an Arduino (from the Serial Monitor), we can make use of
the Serial.available() and Serial.read() functions. First, we check if there's any data available,
and if so, we read it and print it out.
This example will essentially print out whatever you enter in the Serial Monitor (because we
send the data to the board, but we also print it back).
COPY
Explain
int incomingByte = 0; // for incoming serial data
void setup() {
Serial.begin(9600); //initialize serial communication at a 9600 baud rate
}
void loop() {
// send data only when you receive data:
if (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
COPY
Explain
String sendMessage;
String receivedMessage;
void setup() {
Serial.begin(9600); // Initialize the Serial monitor for debugging
Serial1.begin(9600); // Initialize Serial1 for sending data
}
void loop() {
while (Serial1.available() > 0) {
char receivedChar = Serial1.read();
if (receivedChar == '\n') {
Serial.println(receivedMessage); // Print the received message in the Serial monitor
receivedMessage = ""; // Reset the received message
} else {
receivedMessage += receivedChar; // Append characters to the received message
}
}
if (Serial.available() > 0) {
char inputChar = Serial.read();
if (inputChar == '\n') {
Serial1.println(sendMessage); // Send the message through Serial1 with a newline
character
sendMessage = ""; // Reset the message
} else {
sendMessage += inputChar; // Append characters to the message
}
}
}
We start by declaring two String variables for our incoming and outgoing messages.
COPY
String sendMessage;
String receivedMessage;
Inside setup() we initialize both Serial and Serial1 with a baudrate of 9600, establishing a
connection with the computer and the other transmitting Arduino board.
COPY
Serial.begin(9600);
Serial1.begin(9600);
Reading Messages
The core code can be found inside loop(). If a new byte is received, meaning
Serial1.available() is larger than 0 we check the received message.
COPY
while (Serial1.available() > 0) {
...
}
We read a single character from the Serial1 input buffer:
COPY
char receivedChar = Serial1.read();
If a newline character '\n' is encountered, the received message is processed and printed to the
Arduino serial monitor. The receivedMessage is then reset to an empty string to prepare for
the next incoming message.
In programming, the newline character ('\n') is like pressing "Enter" key. It's a special
character that tells the computer, "Move to the next line." In our case we know that a message
is sent after pressing enter which equals the newline character ('n').
COPY
if (receivedChar == '\n') {
Serial.println(receivedMessage);
receivedMessage = "";
}
Send Messages
For sending messages we first check if there are any characters available in the Serial input
buffer. In other words we check if there is any text written inside the serial monitor, in which
case Serial.available() > 0.
COPY
if (Serial.available() > 0) {
...
}
When a newline character '\n' is detected, the message is sent via Serial1. The sendMessage is
then reset to an empty string for the next outgoing message.
COPY
Explain
char inputChar = Serial.read();
if (inputChar == '\n') {
Serial1.println(sendMessage);
sendMessage = "";
}
If the input is not a newline (meaning everything is written on the same line) it's added to the
same message.
COPY
else {
sendMessage += inputChar; // Append characters to the message
}
Control Built-in LED
The following example lets you control the built-in LED by sending UART messages.
Receiver
COPY
Explain
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // set LED pin as output
digitalWrite(LED_BUILTIN, LOW); // switch off LED pin
Explain
void setup() {
pinMode(LED_BUILTIN, OUTPUT); // set LED pin as output
digitalWrite(LED_BUILTIN, LOW); // switch off LED pin
First, we start by setting up our devices setting the built-in LED as OUTPUT and writing an
initial LOW value to it.
COPY
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
Inside setup() we initialize the UART connection by calling Serial1 with a baudrate of 9600.
COPY
Serial1.begin(9600);
If a new byte is received, meaning Serial1.available() is larger than 0 we check the received
message.
COPY
while (Serial1.available() > 0) {
...
}
Next, we read the received byte and check if it equals '1'. If the condition is met, we turn on
the built-in LED by writing HIGH.
COPY
char receivedData = Serial1.read(); // read one byte from serial buffer and save to
receivedData
if (receivedData == '1') {
digitalWrite(LED_BUILTIN, HIGH); // switch LED On
}
If the received byte equals '0' we write LOW to the buit-in LED.
COPY
else if (receivedData == '0') {
digitalWrite(LED_BUILTIN, LOW); // switch LED Off
}
Transmitter
First, we start by setting up our devices setting the built-in LED as OUTPUT and writing an
initial LOW value to it.
COPY
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
Inside setup() we initialize both Serial and Serial1 with a baudrate of 9600, establishing a
connection with the computer and the other transmitting Arduino board. This is because only
the transmitter needs to be connected to the serial monitor.
COPY
Serial.begin(9600);
Serial1.begin(9600);
Inside loop() we check if the written message inside the IDE serial monitor (Serial) equals '1'
and if so we print the same message to Serial1, turning the built-in LED on.
COPY
Explain
if (Serial.read() == '1'){
Serial1.println('1');
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LEDS ON");
}
If the written message inside the IDE serial monitor (Serial) equals '0', we print the same
message to Serial1, turning the built-in LED off.
AuthorKarl Söderby
Last revision08/02/2024
The Nano Family
The Nano Family
The Arduino Nano Family is a series of boards with a tiny footprint. This guide is dedicated
to you who wants to design your own customized hardware for the Nano Family.
This article aims to provide you with technical information that will aid the design of your
own customized Nano hardware.
Documentation
Each Nano Family board has a dedicated documentation page, see the list below:
Nano
Nano Every
Nano 33 BLE
Nano 33 BLE Sense
Nano 33 IoT
Nano RP2040 Connect
Inside the documentation page, you will find design files such as full pinout, CAD and
Fritzing files. You will also find tutorials and compatible libraries with the respective boards
in this page.
Technical Overview
Dimensions
Nano dimensions.
Nano dimensions.
Format Measurement
Width 17.76 mm
Length 43.16 mm
Pitch 2.54 mm
Pitch is the space between the pins (e.g. between A1, A2. This specification is important
when choosing header pins.
More detailed blueprints are available for download through the links below (in .svg format):
Board Nano Nano Every Nano 33 BLE Nano 33 BLE Sense Nano 33 IoT Nano
RP2040 Connect
Processor ATmega328P ATmega4809 nRF52840 nRF52840 SAMD21G18A
RP2040
Radio Module x x NINA-B306 NINA-B306 NINA-W102 NINA-W102
Connectivity x x Bluetooth® Bluetooth® Wi-Fi, Bluetooth® Wi-Fi,
Bluetooth®
Clock Speed 16 Mhz 16 Mhz 64 Mhz 64 Mhz 48 Mhz 133
MHz
Flash Memory 32 KB 48 KB 256 KB 256 KB 264 KB 16 MB
SRAM 2 KB 6 KB 1 MB 1 MB 256 KB 264 KB
EEPROM 1 KB 256 byte x x x x
I/O Voltage 5V 5V 3.3V 3.3V 3.3V 3.3V
There are several embedded sensors on the Nano boards, which can be seen below:
Board Nano Nano Every Nano 33 BLE Nano 33 BLE Sense Nano 33 IoT Nano
RP2040 Connect
IMU x x LSM9DS1 LSM9DS1 LSM6DS3 LSM6DSOX
Microphone x x x MP34DT05 x MP34DT05
Gesture x x x APDS-9960 x x
Light x x x APDS-9960 x x
Color x x x APDS-9960 x x
Pressure x x x LPS22HB x x
Temperature x x x HTS221 x x
Humidity x x x HTS221 x x
Power Considerations
Voltage (3.3V / 5V)
It is important to understand that the Nano family boards operate on different voltage. Any
board with a radio module (Nano 33 BLE, Nano 33 BLE Sense, Nano 33 IoT, Nano RP2040
Connect) operates on 3.3V. The Nano and Nano Every operate on 5V.
As the boards with radio modules operate on 3.3V logic, the 5V pin is connected to headers
via a solder jumper which defaults open. When powering the board via USB, the VIN pin can
be used as a 5V output from the board. This is useful when powering carrier boards requiring
5V and powering the system via USB.
VUSB Pads
The 5V pin is also referred to as "VUSB". The purpose of this pin is to provide direct
connection between USB connector's VUSB (5V) to headers. This can be used to power
devices on a carrier board directly while when using VIN pin this will exhibit a drop due to
the series diodes that prevents VIN to back power the USB. In order to connect VUSB/5V
pin headers, you will need to solder together the VUSB pads on the bottom of the board, as
shown in the image below:
If you solder the VUSB pads, and then power the board via USB, it also enables the 5V pin to
be used. Be very cautious with this, as you risk damaging your board's ICs.
VIN Min/Max
The min/max voltage supply varies between boards. This is important to consider when
choosing the battery source, that you do not exceed the limits to damage the board.
Nano Nano Every Nano 33 BLE Nano 33 BLE Sense Nano 33 IoT Nano RP2040
Connect
7-12V 7-18V 5-18V 5-18V 5-18V 5-18V
Battery Connection
Nano boards can be powered with batteries, but they do not come with a battery charging
circuit or battery protection circuit.
Many LiPo batteries have a built-in protection circuit, but some does not. In short, this means
overdischarge can happen when powering the board with a LiPo battery. See the section
below for more information.
To connect a battery to a Nano board, you will need to use the VIN pin (refer to the VIN min-
max table in the section above).
Pinout
Nano boards largely share the placement of many pins, to make it easy for accessories to be
designed for different Nano boards.
Serial Buses
The Nano Family boards have serial buses attached to the following pins:
Protocol Pins
UART RX,TX
SPI COPI(11), CIPO(12), SCK(13)
I²C SDA(A4), SCL(A5)
The location of these pins are located in the pinout for each board. These are found in the
Resources Section product page of each board.
Carrier Templates
Nano carrier template file.
Nano carrier template file.
In this section, you will find downloadble files that can be used together with various PCB
design programs, such as Altium and Eagle to create your own Nano accessories.
You can either download the connector template, or the solder pads template.
Connector template: empty carrier template with connectors. This is useful if you want to
create a design where you can attach and remove a Nano board easily.
Solder pads template: an empty carrier template with solder pads. This is useful if you want
to create a design where you solder the Nano directly on top of the PCB.
Altium
Below are template files for Altium.
Castellated holes on a Nano board to the left, solder pads on a PCB to the right.
Castellated holes on a Nano board to the left, solder pads on a PCB to the right.
This method is useful for more robust applications, where the Nano board needs to be
permanently attached.
Do not attempt to solder any Arduino Nano boards shipped in single package using the
reflow soldering method. Since Arduino boards are not shipped in a dry pack, the board may
absorb humidity and is thus unsuitable for reflow process. Boards should always be soldered
manually; in case you're interested in using the reflow soldering process for volume
manufacturing, please contact Arduino PRO.
AuthorDario Pennisi
Last revision08/02/2024
This document aims to describe the design philosophy and technical specifications of the
Nicla Form Factor and should be used as a guideline for designing boards and accessories
(shields/carriers) compatible with the form factor.
Nicla family has been designed to fulfill the following key needs:
Low Power
integrated sensing with AI capabilities
wireless communication
partial compatibility with MKR form factor
industrial temperature range
Nicla boards are designed for the following three use cases:
Standalone - Nicla boards can be used as a traditional Arduino board on which the user can
load the final application. In this scenario Nicla's microcontroller is executing the whole
application and its interfaces can be used to communicate to external sensors and actuators
ESLOV peripheral - Nicla boards can be connected through the ESLOV connector,
consisting of 5 pins including an I2C bus, power supply and a GPIO. Through this connector
the board can be interfaced with MKR boards directly or, via adapter cables, to other boards
exposing similar interfaces such as QWIIC, STEMMA/STEMMA QT and GROVE. Nicla
boards are provided with a readymade firmware which abstracts sensors and allows reading
high level measurements processed by Nicla main processor.
MKR Shield - Nicla boards can be fitted on a MKR board as a shield. In this scenario MKR
would be running the main application and would communicate with Nicla through I2C as it
would do via ESLOV.
Wireless - Nicla boards can be battery operated and can communicate wirelessly to a central
processor. As in other use case scenarios Nicla would be processing sensor data and provide
high level processed information.
Mechanical Specification
Board Size and Shape
Nicla form factor shape is a square with 900 mils edge length, excluding the protrusions of
connectors which are meant to be passing though a case. Although board is square its shape
and its connectors are asymmetric to prevent connecting the board in the wrong orientation.
Fins
Nicla fins quotes
Nicla fins quotes
User Interface
Nicla user interface quotes
Nicla user interface quotes
Connectors
Nicla connector quotes
Nicla connector quotes
Power Supply
Nicla boards can be powered from four different power supply rails:
USB connector
ESLOV connector
Vin pin
Battery
When powered from USB, Vin or ESLOV the board can charge the battery if software is
programmed to allow it. All voltages can be present at the same time without causing any
issue as the three voltages are ORed through diodes. Vin pin is directly connected to the
PMIC whereas the other two pass through diodes, which means that ESLOV connector will
not be able to power external devices and is meant primarily as a peripheral.
Nicla boards provide an on board battery charger for single cell LiPo batteries which have a
programmable charge current depending on the specific board. All boards provide sleep
mode functionality which minimizes current draw from battery, keeping the system off until
the reset button is pressed.
External Interfaces
Headers
Header I/O pins are driven by a bidirectional voltage translator that allows decoupling I/O
Voltage from internal CPU voltage. Header pins are referenced to the VDDIO pin which can
either be generated internally with a programmable voltage or fed externally. Board has two
columns of header+castellated pins that are partially compatible with MKR form factor.
Specifically Nicla boards are designed so that a nicla board can be fitted in the first half of the
MKR connectors.
{/* , aligning it as in figure:
Using this connection it's possible to use Nicla as a shield for MKR or Nicla as a controller
for MKR shields provided the shield doesn't use the missing pins.
Pinout
As reported in the table above, Nicla boards have a set of Low Power I/Os. These I/Os are
translated through an 8-Bit Bidirectional Voltage-Level Shifter with Auto Direction Sensing
(check TXB0108 datasheet for further info) and powered by VDDIO_EXT. This shifter is
able to convert logic levels from inputs operating at 3.3V or less down to 1.8V (i.e.
microcontroller operating voltage). However, it does not feature directional pins, since each
input and output connected to it can detect and shift signals in either direction independently
and automatically. As a consequence, the nominal behavior of these pins is guaranteed only
when they are connected to a CMOS logic. In the next section, the interaction with Low
Power pins as input or output is described.
Any input device operating with a voltage less or equal to 3.3V can be connected to the Low
Power pins. The integrated Voltage-Level shifter will automatically detect that an input
device has been connected to the pin and it will guarantee the conversion from 3.3.V to 1.8V,
i.e. microcontroller operating voltage, to properly interface with the Nicla boards.
Any Low Power device can be directly connected to the Low-Power pins in case it does not
absorb any current. The Voltage-Level shifter will automatically detect that an output device
has been connected to the pin and switch the port accordingly. The output voltage is digital
and its value depends on how VDDIO_EXT has been programmed via software:
VDDIO_EXT can be switched off or switched on either at 1.8V or 3.3V. On the other hand,
if the connected output device needs to absorb current (e.g. LEDs, resistive loads, buzzers
etc.), the user should connect a MOSFET or a buffer to guarantee the required current flow.
A reference schematics showing how to connect an LED to a Low-Power pin through a
MOSFET is reported below.
Fins
Nicla boards have a set of fins interleaved among headers that are mainly for debug and
initial programming. These pins are arranged so that they can easily be contacted by inserting
the board in a 1.27mm/50 mil dual row header that would allow firmly contacting the fins
along with header pins, provided no header has been soldered on it. The functions of each fin
is specific to a board but SWD pins for on board processors have been set up so that they are
consistent across boards.
Pinout
Pinout
Pinout
PDM Library
The PDM library allows you to use Pulse-density modulation microphones, found onboard
the Nano RP2040 Connect & Nano 33 BLE Sense boards.
AuthorArduino
Last revision08/02/2024
Overview
The PDM library allows you to use PDM (Pulse-density modulation) microphones, such as
the onboard MP34DT05 on the Arduino Nano 33 BLE Sense.
COPY
#include <PDM.h>
The library takes care of the audio that will be accessible also through the ArduinoSound
library.
Functions
begin()
Description
Initialize the PDM interface.
Syntax
COPY
PDM.begin(channels, sampleRate)
Parameters
channels: the number of channels, 1 for mono, 2 for stereo
sampleRate: the sample rate to use in Hz
Returns
1 on success, 0 on failure
Example
COPY
if (!PDM.begin(1, 16000)) {
Serial.println("Failed to start PDM!");
while (1);
}
end()
Description
De-initialize the PDM interface.
Syntax
COPY
PDM.end()
Parameters
None
Returns
Nothing
Example
COPY
Explain
if (!PDM.begin(1, 16000)) {
Serial.println("Failed to start PDM!");
while (1);
}
//
PDM.end();
available()
Description
Get the number of bytes available for reading from the PDM interface. This is data that has
already arrived and was stored in the PDM receive buffer.
Syntax
COPY
PDM.available()
Parameters
None
Returns
The number of bytes available to read
Example
COPY
Explain
// buffer to read samples into, each sample is 16-bits
short sampleBuffer[256];
//
Syntax
COPY
PDM.read(buffer, size)
Parameters
buffer: array to store the PDM data into
size: number of bytes to read
Returns
The number of bytes read
Example
COPY
Explain
// buffer to read samples into, each sample is 16-bits
short sampleBuffer[256];
//
Syntax
COPY
PDM.onReceive(callback)
Parameters
callback: function that is called when new PDM data is ready to be read
Returns
Nothing
Example
COPY
Explain
// buffer to read samples into, each sample is 16-bits
short sampleBuffer[256];
//
//
void onPDMdata() {
// query the number of bytes available
int bytesAvailable = PDM.available();
Syntax
COPY
PDM.setGain(gain)
Parameters
gain: gain value to use, 0 - 255, defaults to 20 if not specified.
Returns
Nothing
Example
COPY
Explain
// optionally set the gain, defaults to 20
PDM.setGain(30);
Syntax
COPY
PDM.setBufferSize(size)
Parameters
size: buffer size to use in bytes
Returns
Nothing
Example
COPY
Explain
PDM.setBufferSize(1024);
I2S Library
Documentation for usage of the I2S (Inter-IC Sound) protocol on SAMD21 boards.
AuthorArduino
Last revision08/02/2024
Overview
This library allows you to use the I2S protocol on SAMD21 based boards (i.e Arduino or
Genuino Zero, MKRZero or MKR1000 Board).
COPY
#include <I2S.h>
I2S (Inter-IC Sound), is an electrical serial bus interface standard used for connecting digital
audio devices together. It is used to communicate PCM audio data between integrated circuits
in an electronic device.
An I2S bus that follows the Philips standard is made up of at least three wires:
SCK (Serial Clock): is the clock signal also referred as BCLK (Bit Clock Line);
FS (Frame Select): used to discriminate Right or Left Channel data also referred WS (Word
Select);
SD (Serial Data): the serial data to be transmitted;
As detailed below, the device who generates SCK and WS is the Controller.
The SCK line has a frequency that depends on the sample rate, the number of bits for channel
and the number of channels in the following way:
As a general rule of thumb, the higher the sample rate (kHz) and bits per sample, the better
audio quality (when the digital data is converted back to analog audio sound).
I2S Class
begin()
Description
Initializes the I2S interface with the given parameters to enable communication.
Syntax
COPY
I2S.begin(mode, sampleRate, bitsPerSample); // controller device
I2S.begin(mode, bitsPerSample); // peripheral device
Parameters
mode: one between I2S_PHILIPS_MODE, I2S_RIGHT_JUSTIFIED_MODE or
I2S_LEFT_JUSTIFIED_MODE sampleRate: the desired sample rate in Hz - long
bitsPerSample: the desired bits per sample (i.e 8, 16, 32)
Returns
1 if initialization is ok, 0 otherwise
end()
Description
Disables I2S communication, allowing the I2S pins to be used for general input and output.
To re-enable I2S communication, call I2S.begin().
Syntax
COPY
I2S.end()
Parameters
none
Returns
nothing
available()
Description
Get the number of bytes available for reading from the I2S interface. This is data that has
already arrived and was stored in the I2S receive buffer. available() inherits from the Stream
utility class.
Syntax
COPY
I2S.available()
Parameters
none
Returns
the number of bytes available to read
peek()
Description
Returns the next sample of incoming I2S data without removing it from the internal I2S
buffer. That is, successive calls to peek() will return the same sample, as will the next call to
read(). peek() inherits from the Stream utility class.
Syntax
COPY
I2S.peek()
Parameters
None
Returns
The next sample of incoming I2S data available (or 0 if no data is available)
write()
Description
Writes binary data to the I2S interface. This data is sent as a sample or series of samples.
Syntax
COPY
I2S.write(val) // blocking
I2S.write(buf, len) // not blocking
Parameters
val: a value to send as a single sample
Returns
byte
write() will return the number of bytes written, though reading that number is optional.
availableForWrite()
Description
Get the number of bytes available for writing in the buffer without blocking the write
operation.
Syntax
COPY
I2S.availableForWrite()
Parameters
none
Returns
the number of bytes available to write.
onTransmit(handler)
Description
Registers a function to be called when a block of data has been transmitted.
Parameters
handler: the function to be called when data is sent and return nothing, e.g.: void myHandler()
Returns
None
onReceive(handler)
Description
Registers a function to be called when a block of data has been received.
Parameters
handler: the function to be called when the device receives data and return nothing, e.g.: void
myHandler()
Returns
None
EEPROM Library
Documentation for usage of the EEPROM library. EEPROM is a memory whose values are
kept when the board is powered off.
AuthorArduino
Last revision08/02/2024
The microcontroller on the Arduino and Genuino AVR based board has EEPROM: memory
whose values are kept when the board is turned off (like a tiny hard drive). This library
enables you to read and write those bytes.
The supported micro-controllers on the various Arduino and Genuino boards have different
amounts of EEPROM: 1024 bytes on the ATmega328P, 512 bytes on the ATmega168 and
ATmega8, 4 KB (4096 bytes) on the ATmega1280 and ATmega2560. The Arduino and
Genuino 101 boards have an emulated EEPROM space of 1024 bytes.
COPY
#include <EEPROM.h>
Examples
To see a list of examples for the EEPROM library, click the link below:
A Guide to EEPROM
Functions
read()
Description
Reads a byte from the EEPROM. Locations that have never been written to have the value of
255.
Syntax
COPY
EEPROM.read(address)
Parameters
address: the location to read from, starting from 0 (int)
Returns
the value stored in that location (byte)
Example
COPY
Explain
#include <EEPROM.h>
int a = 0;
int value;
void setup()
{
Serial.begin(9600);
}
void loop()
{
value = EEPROM.read(a);
Serial.print(a);
Serial.print("\t");
Serial.print(value);
Serial.println();
a = a + 1;
if (a == 512)
a = 0;
delay(500);
}
write()
Description
Write a byte to the EEPROM.
Syntax
COPY
EEPROM.write(address, value)
Parameters
address: the location to write to, starting from 0 (int)
Returns
none
Note: An EEPROM write takes 3.3 ms to complete. The EEPROM memory has a specified
life of 100,000 write/erase cycles, so you may need to be careful about how often you write
to it.
Example
COPY
Explain
#include <EEPROM.h>
void setup()
{
for (int i = 0; i < 255; i++)
EEPROM.write(i, i);
}
void loop()
{
}
update()
Description
Write a byte to the EEPROM. The value is written only if differs from the one already saved
at the same address.
Syntax
COPY
EEPROM.update(address, value)
Parameters
address: the location to write to, starting from 0 (int)
Returns
none
Note: An EEPROM write takes 3.3 ms to complete. The EEPROM memory has a specified
life of 100,000 write/erase cycles, so using this function instead of write() can save cycles if
the written data does not change often
Example
COPY
Explain
#include <EEPROM.h>
void setup()
{
for (int i = 0; i < 255; i++) {
// this performs as EEPROM.write(i, i)
EEPROM.update(i, i);
}
for (int i = 0; i < 255; i++) {
// write value "12" to cell 3 only the first time
// will not write the cell the remaining 254 times
EEPROM.update(3, 12);
}
}
void loop()
{
}
get()
Description
Read any data type or object from the EEPROM.
Syntax
COPY
EEPROM.get(address, data)
Parameters
address: the location to read from, starting from 0 (int)
data: the data to read, can be a primitive type (eg. float) or a custom struct
Returns
A reference to the data passed in
Example
COPY
Explain
#include <EEPROM.h>
struct MyObject{
float field1;
byte field2;
char name[10];
};
void setup(){
Serial.begin( 9600 );
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.print( "Read float from EEPROM: " );
Syntax
COPY
EEPROM.put(address, data)
Parameters
address: the location to write to, starting from 0 (int)
data: the data to write, can be a primitive type (eg. float) or a custom struct
Returns
A reference to the data passed in
Note: This function uses EEPROM.update() to perform the write, so does not rewrites the
value if it didn't change.
Example
COPY
Explain
#include <EEPROM.h>
struct MyObject {
float field1;
byte field2;
char name[10];
};
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
//One simple call, with the address first and the object second.
EEPROM.put(eeAddress, f);
/** Put is designed for use with custom structures also. **/
//Data to store.
MyObject customVar = {
3.14f,
65,
"Working!"
};
eeAddress += sizeof(float); //Move address to the next byte after float 'f'.
EEPROM.put(eeAddress, customVar);
Serial.print("Written custom data type! \n\nView the example sketch eeprom_get to see how
you can retrieve the values!");
}
Syntax
COPY
EEPROM[address]
Parameters
address: the location to read/write from, starting from 0 (int)
Returns
A reference to the EEPROM cell
Example
COPY
Explain
#include <EEPROM.h>
void setup(){
//Compare contents
if( val == EEPROM[ 0 ] ){
//Do something...
}
}
Description
This function returns an unsigned int containing the number of cells in the EEPROM.
Syntax
COPY
EEPROM.length()
Returns
Number of cells in the EEPROM as an unsigned int.
SoftwareSerial Library
The SoftwareSerial library allows serial communication on other digital pins of an Arduino
board.
AuthorArduino
Last revision08/02/2024
The SoftwareSerial library allows serial communication on other digital pins of an Arduino
board, using software to replicate the functionality (hence the name "SoftwareSerial"). It is
possible to have multiple software serial ports with speeds up to 115200 bps. A parameter
enables inverted signaling for devices which require that protocol.
The version of SoftwareSerial included in 1.0 and later is based on the NewSoftSerial library
by 'Mikal Hart'.
COPY
#include <SoftwareSerial.h>
Limitations of This Library
SoftwareSerial library has the following known limitations:
Examples
SoftwareSerial example: sometimes one serial port just isn't enough!
Two port receive: Work with multiple software serial ports.
Methods
SoftwareSerial()
Create an instance of a SoftwareSerial object. Multiple SoftwareSerial objects may be
created, however only one can be active at a given moment.
Syntax
COPY
SoftwareSerial(rxPin, txPin, inverse_logic)
Parameters
rxPin: the pin on which to receive serial data.
txPin: the pin on which to transmit serial data.
inverse_logic: used to invert the sense of incoming bits (the default is normal logic). If set,
SoftwareSerial treats a LOW (0v on the pin, normally) on the RX pin as a 1-bit (the idle
state) and a HIGH (5V on the pin, normally) as a 0-bit. It also affects the way that it writes to
the TX pin. Default value is false.
Returns
None.
Example
COPY
Explain
#include <SoftwareSerial.h>
Syntax
COPY
mySerial.available()
Parameters
None.
Returns
The number of bytes available to read.
Example
COPY
Explain
#include <SoftwareSerial.h>
#define rxPin 10
#define txPin 11
void setup() {
// Define pin modes for TX and RX
pinMode(rxPin, INPUT);
pinMode(txPin, OUTPUT);
void loop() {
if (mySerial.available() > 0) {
mySerial.read();
}
}
See also
SoftwareSerial()
begin()
isListening()
overflow()
peek()
read()
print()
println()
listen()
write()
begin()
Sets the speed (baud rate) for the serial communication. Supported baud rates are: 300, 600,
1200, 2400, 4800, 9600, 14400, 19200, 28800, 31250, 38400, 57600, and 115200 bauds.
Syntax
COPY
mySerial.begin(speed)
Parameters
speed: the desired baud rate (long). Supported baud rates are: 300, 600, 1200, 2400, 4800,
9600, 14400, 19200, 28800, 31250, 38400, 57600, and 115200 bauds.
Returns
None.
Example
COPY
Explain
#include <SoftwareSerial.h>
#define rxPin 10
#define txPin 11
void setup() {
// Define pin modes for TX and RX
pinMode(rxPin, INPUT);
pinMode(txPin, OUTPUT);
void loop() {
// ...
}
See also
SoftwareSerial()
available()
isListening()
overflow()
peek()
read()
print()
println()
listen()
write()
isListening()
Tests to see if requested software serial object is actively listening.
Syntax
COPY
mySerial.isListening()
Parameters
None.
Returns
Boolean.
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial portOne(10, 11);
void setup() {
// Set the baud rate for the Serial port
Serial.begin(9600);
void loop() {
if (portOne.isListening()) {
Serial.println("portOne is listening!");
}
// ...
See also
SoftwareSerial()
available()
begin()
overflow()
peek()
read()
print()
println()
listen()
write()
overflow()
Tests to see if a SoftwareSerial buffer overflow has occurred. Calling this function clears the
overflow flag, meaning that subsequent calls will return false unless another byte of data has
been received and discarded in the meantime. The SoftwareSerial buffer can hold up to 64
bytes.
Syntax
COPY
mySerial.overflow()
Parameters
None.
Returns
Boolean.
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial portOne(10, 11);
void setup() {
// Set the baud rate for the Serial port
Serial.begin(9600);
void loop() {
if (portOne.overflow()) {
Serial.println("portOne overflow!");
}
// ...
See also
SoftwareSerial()
available()
begin()
isListening()
peek()
read()
print()
println()
listen()
write()
peek()
Return a character that was received on the RX pin of the software serial port. Unlike read(),
however, subsequent calls to this function will return the same character. Note that only one
SoftwareSerial object can receive incoming data at a time (select which one with the listen()
function).
Syntax
COPY
mySerial.peek()
Parameters
None.
Returns
The character read or -1 if none is available.
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial mySerial(10, 11);
void setup() {
// Set the baud rate for the SerialSoftware object
mySerial.begin(9600);
}
void loop() {
char c = mySerial.peek();
}
See also
SoftwareSerial()
available()
begin()
isListening()
overflow()
read()
print()
println()
listen()
write()
read()
Return a character that was received on the RX pin of the SoftwareSerial objecto. Note that
only one SoftwareSerial object can receive incoming data at a time (select which one with the
listen() function).
Syntax
COPY
mySerial.read()
Parameters
None.
Returns
The character read or -1 if none is available.
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial mySerial(10, 11);
void setup() {
// Set the baud rate for the SerialSoftware object
mySerial.begin(9600);
}
void loop() {
char c = mySerial.read();
}
See also
SoftwareSerial()
available()
begin()
isListening()
overflow()
peek()
print()
println()
listen()
write()
print()
Prints data to the transmit pin of the SoftwareSerial object. Works the same as the
Serial.print() function.
Syntax
COPY
mySerial.print(val)
Parameters
val: the value to print.
Returns
The number of bytes written (reading this number is optional).
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial mySerial(10, 11);
int analogValue;
void setup() {
// Set the baud rate for the SerialSoftware object
mySerial.begin(9600);
}
void loop() {
// Read the analog value on pin A0
analogValue = analogRead(A0);
Syntax
COPY
mySerial.println(val)
Parameters
val: the value to print.
Returns
The number of bytes written (reading this number is optional).
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial mySerial(10, 11);
int analogValue;
void setup() {
// Set the baud rate for the SerialSoftware object
mySerial.begin(9600);
}
void loop() {
// Read the analog value on pin A0
analogValue = analogRead(A0);
Syntax
COPY
mySerial.listen()
Parameters
None.
Returns
Returns true if it replaces another.
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial portOne(10, 11);
// Set up a new SoftwareSerial object with RX in digital pin 8 and TX in digital pin 9
SoftwareSerial portTwo(8, 9);
void setup() {
// Set the baud rate for the Serial object
Serial.begin(9600);
void loop() {
// Enable SoftwareSerial object to listen
portOne.listen();
if (portOne.isListening()) {
Serial.println("portOne is listening!");
} else {
Serial.println("portOne is not listening!");
}
if (portTwo.isListening()) {
Serial.println("portTwo is listening!");
} else {
Serial.println("portTwo is not listening!");
}
}
See also
SoftwareSerial()
available()
begin()
isListening()
overflow()
peek()
read()
print()
println()
write()
write()
Prints data to the transmit pin of the SoftwareSerial object as raw bytes. Works the same as
the Serial.write()function.
Syntax
COPY
mySerial.write(val)
Parameters
val: the binary value to print.
Returns
The number of bytes written (reading this number is optional).
Example
COPY
Explain
#include <SoftwareSerial.h>
// Set up a new SoftwareSerial object with RX in digital pin 10 and TX in digital pin 11
SoftwareSerial mySerial(10, 11);
void setup() {
// Set the baud rate for the SerialSoftware object
mySerial.begin(9600);
}
void loop() {
// Send a byte with the value 45
mySerial.write(45);
//Send the string “hello” and return the length of the string.
int bytesSent = mySerial.write(“hello”);
}
See also
SoftwareSerial()
available()
begin()
isListening()
overflow()
peek()
read()
print()
println()
listen()
Last revision08/02/2024
This is a guide for writing clear Arduino examples that can be read by beginners and
advanced users alike. You don't have to code this way, but it helps if you want your code to
be clear to all levels of users. This is not a set of hard and fast rules, it's a set of guidelines.
Some of these guidelines might even conflict with each other. Use your judgment on when
they're best followed, and if you're not sure, ask someone who'll be learning from what you
write what makes the most sense. You might also be interested in the Arduino Style Guide
for Creating Libraries.
If you want to contribute with content for the Arduino Documentation website, please find
instructions in the contribution-templates folder in the Arduino Documentation repository.
Writing a tutorial
Most of this is borrowed from various editors over the years, and here is a list of guidelines
that you can follow when writing code.
The most important users of Arduino are beginners and people who don't care about code, but
about getting projects done.
Think generously about people who know less than you about code. Don't think they should
understand some technical concept. They don't, and they're not stupid for not understanding.
Your code should explain itself, or use comments to do the same. If it needs a complex
concept like registers or interrupts or pointers, either explain it or skip it.
When forced to choose between technically simple and technically efficient, choose the
former.
Introduce concepts only when they are useful and try to minimize the number of new
concepts you introduce in each example. For example, at the very beginning, you can explain
simple functions with no variable types other than int, nor for consts to define pin numbers.
On the other hand, in an intermediate example, you might want to introduce peripheral
concepts as they become useful. Concepts like using const ints to define pin numbers,
choosing bytes over ints when you don't need more than 0 - 255, etc. are useful, but not
central to getting started. So use them sparingly, and explain them when they're new to your
lesson plan.
Put your setup() and your loop() at the beginning of the program. They help beginners to get
an overview of the program, since all other functions are called from those two.
COPY
if (distance > 10) moveCloser();
Instead, use this:
COPY
if (distance > 10) {
moveCloser();
}
Avoid pointers
Avoid #defines
Variables
Avoid single letter variable names. Make them descriptive.
Avoid variable names like val or pin. Be more descriptive, like buttonState or switchPin.
If you want to define pin names and other quantities which won't change, use const ints.
They're less messy than #defines, yet still give you a way to teach the difference between a
variable and a constant.
COPY
pin1 = 2
pin2 = 3
If you need to renumber pins, consider using an array, like this:
COPY
int myPins[] = { 2, 7, 6, 5, 4, 3 };
This allows you to refer to the new pin numbers using the array elements, like this:
COPY
digitalWrite(myPins[1], HIGH); // turns on pin 7
It also allows you to turn all the pins on or off in the sequence you want, like this:
COPY
Explain
for (int thisPin = 0; thisPin < 6; thisPin++) {
digitalWrite(myPins[thisPin], HIGH);
delay(500);
digitalWrite(myPins[thisPin], LOW);
delay(500);
}
Explanation of Code
Here's a good title block:
COPY
/*
Sketch title
The circuit:
* list the components attached to each input
* list the components attached to each output
http://url/of/online/tutorial.cc
*/
Circuits
For digital input switches, the default is to use a pulldown resistor on the switch rather than a
pullup. That way, the logic of a switch's interaction makes sense to the non-engineer.
Keep your circuits simple. For example, bypass capacitors are handy, but most simple inputs
will work without them. If a component is incidental, explain it later.
Last revision08/02/2024
This is a style guide to writing library APIs in an Arduino style. Some of these run counter to
professional programming practice. We’re aware of that, but it’s what’s made it possible for
so many beginners to get started with Arduino easily. So please code with these principles in
mind. If you have suggestions on how to make Arduino libraries clearer for that core
audience, please jump in the discussion.
Be kind to the end user. Assume you are writing an API for an intelligent person who has not
programmed before. Come up with a clear mental model of the concept you’re working with,
and the terms and functions you will use.
Match your API to the underlying capabilities. You don’t want to expose implementation
details to the user but you also don’t want an API that suggests an inaccurate mental model of
the possibilities. For example, if there are only a few possible options for a particular setting,
don’t use a function that takes an int, as it implies you can use any value you want.
Organize your public functions around the data and functionality that the user wants. Quite
often, the command set for a particular electronic module is overly complicated for the most
common uses, or can be re-organized around higher level functionality. Think about what the
average person thinks the thing does, and try to organise your API functions around that.
Adafruit's BMP085 library is a good example. The readPressure() function performs all the
necessary steps to get the final pressure. The library wraps this commonly executed series of
functions into a high-level single command which returns the value the user's looking for in a
format she expects. It abstracts away not only the low-level I2C commands, but also the mid-
level temperature and pressure calculations, while still offering those mid-level functions as
public functions for those who want them.
Use full, everyday words. Don’t be terse with your function names or variables. Use
everyday terms instead of technical ones. Pick terms that correspond to popular perception of
the concept at hand. Don’t assume specialized knowledge. For example, this is why we used
analogWrite() rather than pwm(). Abbreviations are acceptable, though, if they’re in common
use or are the primary name for something. For example, “HTML” is relatively common and
“SPI” is effectively the name of that protocol (“serial-peripheral interface” is probably too
long). (“Wire” was probably a mistake, as the protocol it uses is typically called “TWI” or
“I2C”.)
Avoid words that have different meanings to the general public. For example, to
programmers, an error is a notification that something happened. To the general public, errors
are bad things.
When you have to use a domain-specific term, write a sentence or two describing it to the
general public FIRST. You’ll likely come across a better term, and if not, you’ll have started
the documentation on your library.
Document and comment as you go. When writing examples and documentation, follow the
Writing Style Guide
Use read() to read inputs, and write() to write to outputs, e.g. digitalRead(), analogWrite(),
etc.
Use the Stream and Print classes when dealing with byte streams. If it’s not appropriate, at
least try to use its API as a model. For more on this, see below
For network applications, use the Client and Server classes as the basis.
Use begin() to initialize a library instance, usually with some settings. Use end() to stop it.
Use camel case function names, not underscore. For example, analogRead, not analog_read.
Or myNewFunction, not my_new_function. We've adopted this from Processing.org for
readability's sake.
LONG_CONSTANT_NAMES_FULL_OF_CAPS are hard to read. Try to simplify when
possible, without being terse.
Try to avoid boolean arguments. Instead, consider providing two different functions with
names the describe the differences between them.
Don’t assume knowledge of pointers. Beginning users of C find this the biggest roadblock,
and get very confused by & and *, so whenever you can avoid having them hanging out in
the API, do so. One way is to pass by reference using array notation rather than * notation,
for example.
COPY
void printArray(char* array);
can be replaced by
COPY
void printArray(char[] array);
Though there are some libraries where we pass pointers by using structures like const chars,
avoid anything that requires the user to pass them. For example,rather than:
COPY
foo.readAccel(&x, &y, &z);
use something like this:
COPY
xAxis = adxl.readX();
yAxis = adxl.readY();
zAxis = adxl.readZ();
When using serial communication, allow the user to specify any Stream object, rather than
hard-coding Serial. This will make your library compatible with all serial ports on boards
with multiple (e.g., Mega), and can also use alternate interfaces like SoftwareSerial. The
Stream object can be passed to your library's constructor or to a begin() function (as a
reference, not a pointer). See Firmata 2.3 or XBee 0.4 for examples of each approach.
When writing a library that provides byte-stream communication, inherit Arduino's Stream
class, so your library can be used with all other libraries that accept Stream objects. If
possible, buffer incoming data, so that read() immediately accesses the buffer but does not
wait for more data to arrive. If possible, your write() method should store data to a transmit
buffer, but write() must wait if the buffer does not have enough space to immediately store all
outgoing data. The yield() function should be called while waiting.
Here are a few libraries that are exemplary from Adafruit. She breaks the functions of the
devices down into their high-level activities really well.
https://github.com/adafruit/Adafruit-BMP085-Library
https://github.com/adafruit/DHT-sensor-library
This does a nice job of abstracting from the Wire (I2C) library:
https://github.com/adafruit/RTClib
The text of the Arduino reference is licensed under a Creative Commons Attribution-
ShareAlike 3.0 License. Code samples in the reference are released into the public domain.
Last revision08/02/2024
This document explains how to create a library for Arduino. It starts with a sketch for
flashing Morse code and explains how to convert its functions into a library. This allows
other people to easily use the code that you've written and to easily update it as you improve
the library.
For more information, see the API Style Guide for information on making a good Arduino-
style API for your library.
COPY
Explain
int pin = 13;
void setup()
{
pinMode(pin, OUTPUT);
}
void loop()
{
dot(); dot(); dot();
dash(); dash(); dash();
dot(); dot(); dot();
delay(3000);
}
void dot()
{
digitalWrite(pin, HIGH);
delay(250);
digitalWrite(pin, LOW);
delay(250);
}
void dash()
{
digitalWrite(pin, HIGH);
delay(1000);
digitalWrite(pin, LOW);
delay(250);
}
If you run this sketch, it will flash out the code for SOS (a distress call) on pin 13.
The sketch has a few different parts that we'll need to bring into our library. First, of course,
we have the dot() and dash() functions that do the actual blinking. Second, there's the pin
variable which the functions use to determine which pin to use. Finally, there's the call to
pinMode() that initializes the pin as an output.
You need at least two files for a library: a header file (w/ the extension .h) and the source file
(w/ extension .cpp). The header file has definitions for the library: basically a listing of
everything that's inside; while the source file has the actual code. We'll call our library
"Morse", so our header file will be Morse.h. Let's take a look at what goes in it. It might seem
a bit strange at first, but it will make more sense once you see the source file that goes with it.
The core of the header file consists of a line for each function in the library, wrapped up in a
class along with any variables you need:
COPY
Explain
class Morse
{
public:
Morse(int pin);
void begin();
void dot();
void dash();
private:
int _pin;
};
A class is simply a collection of functions and variables that are all kept together in one place.
These functions and variables can be public, meaning that they can be accessed by people
using your library, or private, meaning they can only be accessed from within the class itself.
Each class has a special function known as a constructor, which is used to create an instance
of the class. The constructor has the same name as the class, and no return type.
You need a couple of other things in the header file. One is an #include statement that gives
you access to the standard types and constants of the Arduino language (this is automatically
added to normal sketches, but not to libraries). It looks like this (and goes above the class
definition given previously):
COPY
#include "Arduino.h"
Finally, it's common to wrap the whole header file up in a weird looking construct:
COPY
Explain
#ifndef Morse_h
#define Morse_h
#endif
Basically, this prevents problems if someone accidentally #include's your library twice.
Finally, you usually put a comment at the top of the library with its name, a short description
of what it does, who wrote it, the date, and the license.
COPY
Explain
/*
Morse.h - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Released into the public domain.
*/
#ifndef Morse_h
#define Morse_h
#include "Arduino.h"
class Morse
{
public:
Morse(int pin);
void begin();
void dot();
void dash();
private:
int _pin;
};
#endif
Now let's go through the various parts of the source file, Morse.cpp.
First comes a couple of #include statements. These give the rest of the code access to the
standard Arduino functions, and to the definitions in your header file:
COPY
#include "Arduino.h"
#include "Morse.h"
Then comes the constructor. Again, this explains what should happen when someone creates
an instance of your class. In this case, the user specifies which pin they would like to use. The
constructor records that in a private variable for use in the other functions:
COPY
Morse::Morse(int pin)
{
_pin = pin;
}
There are a couple of strange things in this code. First is the Morse:: before the name of the
function. This says that the function is part of the Morse class. You'll see this again in the
other functions in the class. The second unusual thing is the underscore in the name of our
private variable, _pin. This variable can actually have any name you want, as long as it
matches the definition in the header file. Adding an underscore to the start of the name is a
common convention to make it clear which variables are private, and also to distinguish the
name from that of the argument to the function (pin in this case).
Next, you'll create a begin() function to handle hardware configuration. This will be called
from the setup() function of the sketch. Hardware configuration is done in a dedicated
function instead of the constructor because the hardware has not yet been initialized at the
time the constructor code is executed. In our library, we need to set the pin as an output:
COPY
void Morse::begin()
{
pinMode(_pin, OUTPUT);
}
Then comes the actual code from the sketch that you're turning into a library (finally!). It
looks pretty much the same, except with Morse:: in front of the names of the functions, and
_pin instead of pin:
COPY
Explain
void Morse::dot()
{
digitalWrite(_pin, HIGH);
delay(250);
digitalWrite(_pin, LOW);
delay(250);
}
void Morse::dash()
{
digitalWrite(_pin, HIGH);
delay(1000);
digitalWrite(_pin, LOW);
delay(250);
}
Finally, it's typical to include the comment header at the top of the source file as well. Let's
see the whole thing:
COPY
Explain
/*
Morse.cpp - Library for flashing Morse code.
Created by David A. Mellis, November 2, 2007.
Updated by Jason A. Cox, February 18, 2023.
Released into the public domain.
*/
#include "Arduino.h"
#include "Morse.h"
Morse::Morse(int pin)
{
_pin = pin;
}
void Morse::begin()
{
pinMode(_pin, OUTPUT);
}
void Morse::dot()
{
digitalWrite(_pin, HIGH);
delay(250);
digitalWrite(_pin, LOW);
delay(250);
}
void Morse::dash()
{
digitalWrite(_pin, HIGH);
delay(1000);
digitalWrite(_pin, LOW);
delay(250);
}
And that's all you need (there's some other nice optional stuff, but we'll talk about that later).
Let's see how you use the library.
First, make a Morse directory inside of the libraries sub-directory of your sketchbook
directory. Copy or move the Morse.h and Morse.cpp files into that directory. Now launch the
Arduino environment. If you open the Sketch > Import Library menu, you should see Morse
inside. The library will be compiled with sketches that use it. If the library doesn't seem to
build, make sure that the files really end in .cpp and .h (with no extra .ino, .pde or .txt
extension, for example).
Let's see how we can replicate our old SOS sketch using the new library:
COPY
Explain
#include <Morse.h>
Morse morse(13);
void setup()
{
morse.begin();
}
void loop()
{
morse.dot(); morse.dot(); morse.dot();
morse.dash(); morse.dash(); morse.dash();
morse.dot(); morse.dot(); morse.dot();
delay(3000);
}
There are a few differences from the old sketch (besides the fact that some of the code has
moved to a library).
First, we've added an #include statement to the top of the sketch. This makes the Morse
library available to the sketch and includes it in the code sent to the board. That means if you
no longer need a library in a sketch, you should delete the #include statement to save space.
COPY
Morse morse(13);
When this line gets executed (which actually happens even before the setup() function), the
constructor for the Morse class will be called, and passed the argument you've given here (in
this case, just 13).
Notice that our setup() now has a call to morse.begin() which configures the pin that was set
in the constructor.
Finally, to call the dot() and dash() functions, we need to prefix them with morse. - the name
of the instance we want to use. We could have multiple instances of the Morse class, each on
their own pin stored in the _pin private variable of that instance. By calling a function on a
particular instance, we specify which instance's variables should be used during that call to a
function. That is, if we had both:
COPY
Morse morse(13);
Morse morse2(12);
then inside a call to morse2.dot(), _pin would be 12.
If you tried the new sketch, you probably noticed that nothing from our library was
recognized by the environment and highlighted in color. Unfortunately, the Arduino software
can't automatically figure out what you've define in your library (though it would be a nice
feature to have), so you have to give it a little help. To do this, create a file called
keywords.txt in the Morse directory. It should look like this:
COPY
Morse KEYWORD1
begin KEYWORD2
dash KEYWORD2
dot KEYWORD2
Each line has the name of the keyword, followed by a tab (not spaces), followed by the kind
of keyword. Classes should be KEYWORD1 and are colored orange; functions should be
KEYWORD2 and will be brown. You'll have to restart the Arduino environment to get it to
recognize the new keywords.
It's also nice to provide people with an example sketch that uses your library. To do this,
create an examples directory inside the Morse directory. Then, move or copy the directory
containing the sketch (let's call it SOS) we wrote above into the examples directory. (You can
find the sketch using the Sketch > Show Sketch Folder command.) If you restart the Arduino
environment (this is the last time, I promise) - you'll see a Library-Morse item inside the File
> Sketchbook > Examples menu containing your example. You might want to add some
comments that better explain how to use your library.
If you'd like to check out the complete library (with keywords and example), you can
download it: Morse.zip.
If you'd like to make your library available to others in Arduino's Library Manager you will
also have to include a library.properties file. Check out the library specification for more info
on that. For general questions on the Arduino Library Manager, see the FAQ.
If you have any problems or suggestions, please post them to the Software Development
forum.
For more information, see the API Style Guide for information on making a good Arduino-
style API for your library.