Arduino & Co - Measure, Control, and Hack
Arduino & Co - Measure, Control, and Hack
books books
o All rights reserved. No part of this book may be reproduced in any material form, including
photocopying, or storing in any medium by electronic means and whether or not transiently or
incidentally to some other use of this publication, without the written permission of the copyright holder
except in accordance with the provisions of the Copyright Designs and Patents Act 1988 or under the
terms of a licence issued by the Copyright Licencing Agency Ltd., 90 Tottenham Court Road, London,
England W1P 9HE. Applications for the copyright holder's permission to reproduce any part of the
publication should be addressed to the publishers.
o Declaration
The Author and Publisher have used their best efforts in ensuring the correctness of the information
contained in this book. They do not assume, and hereby disclaim, any liability to any party for any loss
or damage caused by errors or omissions in this book, whether such errors or omissions result from
negligence, accident, or any other cause.
Elektor is part of EIM, the world's leading source of essential technical information and electronics products for pro
engineers, electronics designers, and the companies seeking to engage them. Each day, our international team develops
and delivers high-quality content - via a variety of media channels (including magazines, video, digital media, and social
media) in several languages - relating to electronics design and DIY electronics. www.elektormagazine.com
Arduino & Co
Measure, Control, and Hack
e er ri ith me ro M n o r
●
o ert ont ei er
Arduino & Co – Measure, Control, and Hack
4
Foreword
Foreword
I remember it like it was yesterday. I paged through the electronics catalog that had
just arrived, and saw a new “home computer,” which fascinated me immediately. Not
yet available, but announced, it would retail for the equivalent of over 600 euro – the
Commodore 64. I knew immediately: One day, I would buy one!
As you can guess, I’m not the youngest anymore. It must have been around 1982, and
I was still in school. Two years later, that time came, and I could finally afford the C64.
Back then, we programmed in the BASIC programming language, or directly in
assembler. I controlled everything I could with the C64, because it had a “User Port”
offering 8 data lines, all of which could be used as digital inputs and outputs.
Today, for not much more than a couple of euro, you can get small microcontroller
boards, which are much faster, and in some areas can do much more than the home
computers of back then. The small Pro Mini board with an ATmega328P microcontroller
has no keyboard or video output, of course. But, otherwise, it’s a fully-fledged, freely-
programmable small computer with numerous input and output pins, which you can use
to measure, control, and switch things as you please. That’s exactly what this book is
about: Simple, inexpensive solutions for every purpose.
Robert Sontheimer
Acknowledgments
I’d like to thank Timo Missel for numerous small jobs on this book, Matthias Abele for his
tips and corrections, and, last but not least, Mr. Denis Meyer from Elektor Publishing,
who was at my side in word and deed throughout the book project.
For this English-language edition, my special thanks also go to the translator, Mr. Brian
Tristam Williams, for his good work and cooperation.
Table of Contents
Foreword.................................................................................................................................. 5
Chapter 1 • ATmega boards............................................................................................ 14
1.1 The Pro Mini form factor..................................................................................14
A computer for a few euro............................................................................................ 15
5 V / 16 MHz and 3.3 V / 8 MHz versions............................................................... 16
ATmega328P and ATmega168PA................................................................................ 16
Pin layouts.......................................................................................................................... 17
1.2 Uno versions.......................................................................................................................... 18
1.3 LilyPads and similar............................................................................................................ 19
16 MHz LilyPads.................................................................................................................. 20
1.4 The Nano board.................................................................................................................... 20
Chapter 2 • USB adapter with serial interface ..........................................................21
2.1 USB adapters based on the CP2102 ............................................................................. 21
2.1.1 Project: Universal serial adapter cable ........................................................ 22
Construction............................................................................................................. 22
Tip: Neat soldering............................................................................................ 24
Usage......................................................................................................................... 26
2.1.2 Serial Micro USB adapter................................................................................... 26
Chapter 3 • Buying tips.....................................................................................................27
3.1 Local suppliers and domestic mail order companies .............................................. 27
3.1.1 Conrad Electronic................................................................................................. 27
3.1.2 Reichelt Elektronik............................................................................................... 28
3.1.3 Other online suppliers ........................................................................................ 28
3.2 Big International online stores .....................................................................28
3.2.2 Ebay........................................................................................................................... 28
Search settings....................................................................................................... 29
Security on Ebay..................................................................................................... 29
3.2.3 Amazon.................................................................................................................... 29
3.2.4 AliExpress................................................................................................................ 30
AliExpress shipping costs..................................................................................... 30
Buyer Protection on AliExpress........................................................................... 30
3.3 PayPal payment service ..................................................................................................... 31
3.4 Customs................................................................................................................................... 32
3.5 Caution: Pitfalls!................................................................................................................... 32
Fake items............................................................................................................................ 32
False information................................................................................................................ 33
False promises.................................................................................................................... 33
3.6 Buying basic equipment.................................................................................. 34
3.6.1 The necessary tools............................................................................ 34
Project: The simplest soldering station in the world ...............................34
Additional tools...................................................................................................... 36
3.6.2 ATmega boards.....................................................................................36
Serial adapters...................................................................................................... 36
3.6.3 Power supply.........................................................................................37
Mains power supplies.......................................................................................... 37
The thing about current and voltage................................................................. 37
Disposable or rechargeable battery ............................................................... 39
Lithium-ion rechargeables................................................................................. 39
Warning: Fake batteries..................................................................................... 40
Charge controller with protection circuit ..................................................... 41
3.6.4 Standard components........................................................................ 41
Resistors.................................................................................................................. 41
The E12 series......................................................................................................... 41
Capacitors................................................................................................................ 42
LEDs........................................................................................................................... 43
Transistors............................................................................................................... 43
Buzzer....................................................................................................................... 44
Jumper wires.......................................................................................................... 44
3.6.5 Measuring tools....................................................................................45
Multimeter............................................................................................................... 45
Infrared thermometer......................................................................................... 45
Vernier calipers...................................................................................................... 46
Chapter 4 • Optimal construction ..................................................................................47
4.1 Construction on breadboard............................................................................................ 47
4.2 Point-to-point construction ............................................................................................... 48
4.3 The thumbtack technique................................................................................................. 49
4.4 Perfboards............................................................................................................................... 49
Hole matrix.......................................................................................................................... 49
Stripboard............................................................................................................................ 50
Other grid arrangements.................................................................................................. 50
4.5 Construction on printed circuit board ........................................................................... 51
4.6 Pin header connectors........................................................................................................ 52
Coded connections............................................................................................................. 52
Chapter 5 Programming....................................................................................................53
5.1 The Arduino-platform......................................................................................................... 53
5.2 Our first program................................................................................................................. 54
Syntax: setup() and loop() functions ....................................................................... 54
Sketch: Our first program............................................................................................. 55
5.3 Uploading programs............................................................................................................ 56
5.4 Downloading the programs.............................................................................................. 57
Chapter 6 • Board inputs and outputs......................................................................... 58
Customization................................................................................................................. 118
Syntax: Bitwise operators.......................................................................................... 119
Sketch: Adjustable constant-current source ....................................................... 120
Calibration......................................................................................................................... 125
8.5 Project: Lithium-ion battery testing and charging station ................127
Construction on a PCB................................................................................................. 128
Construction using thumbtacks ................................................................................ 129
Supplying power via USB ............................................................................................ 132
Separate power supply................................................................................................ 132
Cooling............................................................................................................................... 133
Sketch: Lithium-ion testing and charging station ............................................. 134
Current and voltage specifications.......................................................................... 149
Serial output.................................................................................................................... 151
Internal resistance........................................................................................................ 152
Calibration......................................................................................................................... 153
8.6 Project: Adjustable current source with limits ..................................... 155
Time vs charge amount............................................................................................... 155
Sketch: Adjustable current source with limits ....................................................157
Power supply................................................................................................................... 158
Serial output..................................................................................................................... 158
Cooling............................................................................................................................... 159
Default values, limits.................................................................................................... 160
Calibration......................................................................................................................... 161
Chapter 9 • Controlling motors ....................................................................................163
9.1 DC motors.......................................................................................................... 163
9.1.1 Transistor control............................................................................................... 163
9.1.2 Speed control using PWM ............................................................................... 164
9.1.3 Forward and reverse with an H-bridge ...................................................... 165
The L9110S............................................................................................................ 165
The L298N.............................................................................................................. 166
9.1.4 Full control using H-bridge and PWM ......................................................... 167
Syntax: min() and max() functions............................................................ 168
Sketch: Full motor control.............................................................................. 168
9.2 Stepper motors................................................................................................ 170
9.2.1 How it works........................................................................................................ 170
Bipolar and unipolar versions........................................................................ 171
Full- and half-step operation......................................................................... 172
Actual stepper motors...................................................................................... 173
9.2.2 The 28BYJ-48...................................................................................................... 174
Control using a ULN2003 driver board .......................................................175
Control using 4 transistors ............................................................................. 176
Tip: Stepper motor under battery operation ........................................... 177
Sketch: 28BYJ-48 stepper motor control.................................................. 177
10
11
12
13
Fig. 1b: An Uno board, 3 different Pro Mini boards, a LilyPad and a Nano
The picture shows different boards using the ATmega328P. Each has a quartz crystal for
generating its clock signal. Most have a voltage regulator to supply their chips with a
clean, regulated power source (5 V or 3.3 V). The Arduino Uno and Arduino Nano
boards also have an integrated USB interface, while the Pro Mini and LilyPad boards
require separate USB adapters.
It doesn’t have a USB interface. Because you use that port only to upload your
completed programs to the board, it’s wasteful to have it on every board only to be
sitting there unused in the completed project. Instead, we use a separate USB adapter
as the necessary interface, which we can reuse for any of our other boards. The
software can be modified and updated at any time simply by plugging in the adapter
and connecting it to our PC.
14
With its small form factor and omission of the USB interface, the Pro Mini board is
astonishingly cheap. Nevertheless it’s everything built-in, that you need, from processor
to EEPROM – a full-featured computer ready to be programmed for measurement and
control. Directly from China, you could get them a few years ago for under 2 euro.
Since then, the prices have risen occasionally.
Figure 1.1a shows different Pro Mini boards available directly from China. They usually
come with the necessary pin headers. There are generally 2×2 (i.e. 4) different
versions, as can be seen in the following table:
ATmega328P ATmega168PA
32 kB Program memory 16 kB Program memory
2 kB RAM 1 kB RAM
1 kB EEPROM 512 B EEPROM
Version for simple
5 volts Standard version for
projects with mains
16 MHz projects with mains power
power
3,3 volts Suitable for battery- Suitable for simple
8 MHz operated projects battery-operated projects
15
Accordingly, the 16 MHz versions have a 5 V regulator on board, while the 8 MHz
versions are 3.3 V. The RAW input voltage to the regulator may be as high as 12 V. To
keep the regulation as efficient as possible, it’s best to have a voltage that’s marginally
higher (ideally a maximum margin of 3 V) than the regulated output voltage (the 5 V or
3.3 V). That means, for example, a 6 V supply for the 5-volt version, and a 5 V supply
for the 3.3-volt version.
For the sake of simplicity, I’ll dispense with the suffixes (“P” and “PA”) from here on –
“ATmega328” and “ATmega168” are clear enough.
Connections
The following abbreviations are used to identify the individual connections:
RAW: The input to the voltage regulator, which can be as high as 12 V. From this, the
microcontroller’s actual operating voltage, VCC, is created.
RST: Reset pin. This is tied to VCC via a resistor. The Reset button connects this input
to ground and causes the chip to reset.
0 – 13: The digital inputs and outputs. 0 and 1 also serve as serial TX and RX.
16
Pin layouts
Another difference between Pro Mini boards is in the positions and layout of the
connection pins. Fortunately, the variations are limited. Here are a couple of popular
examples:
Fig. 1.1b
The pin layout for the upper and lower rows is identical for all Pro Mini boards. On the
right side, the connections (seen here with a pin header attached) are for the serial-to-
USB adapter. The analog inputs, A4 to A7, are on the left side of this board.
Fig. 1.1c
Here, pins A4 to A7 are located on the inner part of the board. The rest of the pinout is
identical.
17
Fig. 1.1d
Here, note that the pins for the serial port (right) are reversed. The adapter must thus
be connected the other way around.
Fig. 1.1e
In this example, the serial port pins are also reversed. On the left, adjacent to pins A4
to A7 are a few more pins, but these are already available on the bottom row. MOSI,
MISO, and SCK (identical to Pins 11, 12, and 13) can serve as an SPI bus if required.
(This is a different kind of serial port, which we’ll discuss on page 256.)
18
Instead of male pin headers, the Uno boards have female header sockets for the
individual pins. This is very practical for adding various peripheral expansion “shields”
that are available for the Uno series. For us, however, these headers are sometimes a
disadvantage.
In any event, all of these boards may be used for the projects in this book, as long as
you have access to at least 5 volts. For battery-powered applications, running at 8 MHz
on 3.3 V is more appropriate.
Running at 8 MHz with no voltage regulator, the ATmega328 can operate on between
2.7 V and 5.5 V. This is ideal for lithium-ion batteries, which deliver around 4.2 volts
when fully charged, and about 2.8 V when empty. They should not be drained any
further than that.
19
A degree of battery protection can be achieved by using the ATmega’s standby mode
when the battery is almost empty. Then, only a few μA are used (i.e. practically
nothing). A more in-depth explanation is given in Chapter 13.3, on Page 271.
Even the inexperienced solderer will get familiar with the LilyPad quickly. The
clearly-laid-out, large connections invite one to practice. LilyPads can be obtained
directly from China for around 3 euro.
16 MHz LilyPads
Officially, LilyPads operate at 8 MHz, but that doesn’t bother some Chinese
manufacturers, who now prefer to offer 16 MHz LilyPads. For applications that need high
speeds, this may be optimal, but usually the higher clock frequency is a drawback. The
current consumption is higher, and thus, according to the datasheet, the microcontroller
needs at least 4 volts to work at 16 MHz. We might be able to get by with about half a
volt less, so these versions can also be run on batteries, but with restrictions.
When we upload our programs, we have to ensure that the Arduino IDE is aware of
LilyPads. For this, we must select a Pro Mini board and then specify a clock frequency of
8 MHz. This is important, otherwise serial ports and delay functions, for example, will
run at double speed.
The USB interface described in the next chapter is already integrated in the Nano board.
20
While the program is running, it’s also possible for the board to send serial data, which
can then be monitored in a window on the PC (using the so-called “Serial Monitor”).
This is often useful for debugging purposes. The program still runs without the serial
connection, but the data is simply sent into the void.
Additionally, such an adapter can also be used to supply power to the board, but a
regular USB cable (without the serial interface) can serve the same purpose if the
power is to be supplied via USB.
All of these adapters (or converters, as it should be) have a special IC that enables the
conversion between USB and traditional serial protocol. This is like the standard RS-232
interface, but at logic levels of either 5 V or 3.3 V. These are often called UARTs. The
most commonly-used ICs for the purpose are the CH340, the PL2303, and the CP2102.
21
Another advantage offered by this adapter is the DTR connection, which we can make
good use of. One should pay attention to this when purchasing – the most versatile
converter will have the following 6 connections:
DTR
RX (or RXD)
TX (or TXD)
+5 V
GND (minus or ground)
+3,3 V
Fig. 2.1b: Serial adapter based on the CP2102
Direct from China, these adapters often cost less than one euro. There are versions with
large standard USB connectors, which one can plug directly into a desktop or laptop
USB port. Others have a Micro USB port, which require the use of a Micro USB cable.
These are suitable for permanently connecting an adapter to the Pro Mini. The USB
cable then serves as an interface you can plug into the PC. If the adapter is only needed
for uploading programs to the board, then I prefer to get rid of the unnecessary
additional connectors (via the USB cable) and instead opt for the adapter with the
larger standard USB-A connector, which can easily be used as a pluggable adapter
cable, as in the project that follows.
Construction
We need:
22
The red and black wires are important. Instead of yellow, green, and blue, one may opt
for other colors; all that matters is that all 5 colors are clearly distinguishable from each
other. The wire lengths are also up to you – I would use at least 1 meter. The signal still
comes through clearly at lengths of up to 3 meters. In order to bundle them into one,
neat cable, braiding is a good method, but the complete cable will then end up being a
bit shorter than the individual wires.
Figure 2.2.1b shows the adapters’ wiring. The dashed lines have no fixed, soldered
connections, but can rather be plugged into the USB adapters later. For example, one
could switch the VCC wire between 3.3 V and 5 V.
Once the 5 wires have been cut to the same length, it’s best to start on the side of the
board with the 6-pin socket that will later be plugged into the Pro Mini’s pins. For this, a
BL 1 6 Z connector is suitable, but the higher-quality BL 1 6 G is even better, as it has
gold-plated contacts. Since such connectors tend to melt or warp during soldering,
moving the contacts, I highly recommend connecting each female socket to its male pin
counterpart before soldering (the male connector may even be significantly longer with
more contacts) to keep everything in place.
Now, the wires’ ends are stripped to about 2 mm (not longer than the socket’s solder
connections), and then soldered into place.
23
Once the wires and sockets are tinned, the connectors should be fixed in place
somehow (e.g. using a weight or a clamp), so that the soldering area is easily
accessible. Before that comes a small piece of heat shrink tubing (somewhat longer
than the pins to be soldered).
Now, we have 6 contacts, but only 5
wires. This is because the black
ground wire (GND) is connected to the
first two pins. The best way to handle
this is to hold the wire between the
two pins and apply enough solder to
create a large solder joint between the
Fig. 2.1.1c: Soldered; loose heat shrink two Pins.
24
Once a piece has been braided, use your fingers to separate the rest of the
wires in the unbraided cable remaining. This is, unfortunately, a bit tedious
when the remaining cable is still quite long.
Now, the individual wires are soldered to the USB adapter. Note: Don't tin all of the
pins, as the red “plus” wire and the DTR wire are only connected when in use, not
soldered. The RX and TX cables must be crossed over; that is, the wire that goes to RX
on the other end of the cable must go to TX on the adapter side, and vice-versa. (There
may be another letter after the RX and TX abbreviations, but we can ignore these.) For
the black ground wire, there’s only a single pin on the adapter side. Note: Don’t forget
to put the heat shrink tubing on the wires before soldering!
25
Usage
Depending on the board in use and the required operating voltage, the red “plus” wire
must be connected either to the 3.3 V pin or the 5 V pin. Should the board be supplied
with some other source of power, the red wire is not connected at all.
The DTR wire is needed so that the board can be reset automatically when required.
When uploading a program, the board must be reset at the correct point at the
beginning of the upload. One can also do this manually, by holding the Reset switch and
letting it go at the right point in time, but it’s much simpler to let the DTR wire handle
this automatically.
Should you need to use the USB in the application, e.g. to send data from the board to
the PC (once the board has been programmed with your application), it’s best to
disconnect the DTR wire. In this way, accidental reset can be avoided. The power
supplied by the red wire is only needed if the board is not otherwise supplied with
power (e.g. via the board’s RAW input).
One may also make use of the Nano version, which has such a converter built in
(although it makes use of the CH340 chip).
26
In this chapter, I’d like to offer some tips and suggestions for optimal component
purchases in order to save money and avoid problems. I’m independent, so I don’t get
anything in return from any of the companies mentioned below. In today’s digital world,
however, many things can change at any time. Therefore, please understand that, as an
author, I can’t guarantee all information, but I do want to pass on my extensive
experience in this area.
Prices also can change at any time. In 2017, for example, I actually bought some
ATmega328-based Pro Mini boards directly from China for 1.03 euro. Since then, the
prices have spiked again, for now. Therefore, the information on prices in this book can
only serve as a rough guide.
In big cities, there are usually electronic stores that offer ranges of electronics with
varying levels of comprehensiveness.
All these offerings are naturally different in every country. Nevertheless, I would like to
discuss some of them, especially those that have also established an international
trade, and are known beyond the country's borders.
27
3.2.2 Ebay
Ebay does not sell items itself, but is merely a platform for many
dealers and private sellers. They’re located around the world,
especially in China, and offer their products from there – that’s
where we find the cheap Pro Mini boards and the other
components we’ll need for our projects. Since the shipping is mostly from China, the
delivery time may often be over a month, depending on your destination address. Most
of the items can also be found at local dealers, but often at higher prices.
28
Search settings
If you type in a search on ebay.com, numerous settings appear above and to the left of
the search results. These enable you to limit search results. Under “Item Location,” for
example, you can often limit the search to items that are shipped from your own
country or region, depending on your location. (Don’t confuse the location settings on
ebay.com with the regional Ebay sites, such as ebay.de, ebay.co.uk, or ebay.com.au –
these local sites show only items that are available for sale on them.) If you select
“Worldwide,” items from other country sites will also appear, sometimes even in other
currencies. You may also opt to use the local country sites as well.
Also you can sort items by using the options right above the search results. The setting
“Lowest price incl. shipping” makes sense if you’re ordering only one item. If you need
to have 3 or 5 Pro Mini boards in stock, it’s best to sort by lowest price (without
shipping). Then, take the resulting shipping costs into account when comparing.
There are countless other search settings, which I won’t go into individually here,
except to say: You can also click on “Advanced” to the right of the “Search” button, and
get many additional search options.
Security on Ebay
Since you don’t know the sellers on Ebay, having comprehensive buyer protection is
important. However, you only get this on Ebay if you pay using PayPal. This payment
service acts as a trustee. If the goods don’t arrive, are defective, or don’t match their
descriptions, you can file a “Case” and claim Buyer Protection.
3.2.3 Amazon
In contrast to Ebay, Amazon is not merely a platform for
other sellers. Amazon also sells a lot of products itself.
Many items available there can be purchased directly from
Amazon, as well as from other sellers. A lot of the sellers store their products at
Amazon warehouses, make use of Amazon’s logistics, and Amazon takes care of the
shipping. As with Ebay, there are also many Chinese sellers who ship their goods
directly from China. Once again, for the microcontroller boards and other components
we need, these dealers usually offer the lowest prices. The downside is the lengthy
shipping times you can expect from China.
There are also black sheep among Amazon’s sellers, but, as a customer, you are still on
the safe side, as Amazon acts as a trustee (much like PayPal). Valid complaints are
almost always dealt with without a hitch.
29
3.2.4 AliExpress
AliExpress belongs to the Chinese Alibaba group, and is
a gigantic platform for international trade – it’s like a
Chinese version of Ebay. Here, we can find microcon-
troller boards, sensors, and other components in huge quantities and at the lowest
prices you’ll find. I order almost everything from AliExpress, even though the goods
often take over a month to arrive.
On the site, you can select different languages, but the site works best in English.
Communication with the sellers (if it’s at all necessary) is in English as well.
Searches on AliExpress can be a bit tricky. Most of the time, hundreds or thousands of
items will come up, most of which will disappear again when you sort by price.
Sometimes it helps to use different search terms. However, you have to select your sort
criteria after each search. Often, narrowing the search by using additional terms does
not result in fewer search results, as one would expect, but many more. If you click on
an offer that you found, it’s worthwhile to check right at the bottom of that product’s
page, at the “Seller Recommendations” and “More To Love” sections. Often, the item
you’re searching for can be found even cheaper there.
This has changed significantly. With today’s buyer protection, the bad joke is now an
insane joke. By this, I mean that, even if you read in the tracking that your package
never left China, AliExpress decides in favor of the seller, with the terse remark that the
goods have been delivered, and even if a seller cheats and sells a completely useless
product, at best, you’ll get the right to send the goods back to China at your own
expense, which, from most countries, usually costs more than the goods did.
30
Unfortunately, there’s no defense against this at all. There’s an appeal function against
dispute decisions, but you won’t achieve anything with it. Further, there’s no way to
enter into a dialogue with AliExpress. You can chat with their “Eva” service, but it turns
out to be just a primitive bot – you’d have better luck talking to a slice of toast.
However, there’s a way out: AliExpress now offers PayPal payments, so you can make
use of PayPal’s buyer protection. The important thing is that you file your claims with
PayPal, not AliExpress. Without this protection, I would strongly recommend against
buying from AliExpress. With it, I can still recommend AliExpress, as most of the
merchants there are still reputable, and the prices are unbeatable.
Recently, however, PayPal’s buyer protection has become absurd when it comes to
orders from China. Even in the case of a valid complaint, it’s happened that PayPal has
insisted that the goods be sent back to China in order to get a refund. Initially, you have
to pay the return shipping costs yourself, but PayPal offers to reimburse those costs (up
to 12 times a year). The absurd part is that, even if the return shipping costs over
5 times a much as the entire order, this is the only way to do it in most cases, so PayPal
would rather pay 5 times as much (from its own pocket) than simply granting the
refund.
Alongside the business transactions, PayPal also offers a way to pay small amounts to
private individuals as a means of cashless payment. For example, if you’ve lent a friend
money or paid for something for someone, it can be repaid quite simply via PayPal.
There, it will stay in the user’s account until it’s spent again.
Importantly, you have to distinguish between these two payment types on PayPal.
Payments with “PayPal Buyer Protection” when you’re paying for goods require a small
fee, but the buyer won’t notice it, because it’s paid by the seller. There’s also the option
to “Send Money to Friends and Family.” For this, there’s no fee at all, but there’s also no
buyer protection. Therefore, as a buyer, you should always decline the latter payment
method unless you know and trust the seller one hundred percent.
31
3.4 Customs
Whether import duties are incurred depends on where you buy from and where the
product is shipped. Worldwide, there are several common markets with customs unions
(e.g. the EU) within which no customs duties have to be paid. For orders from China,
this is often different. To ship to the EU, for example, there are customs fees or orders
of over €150, but, even for smaller orders, the VAT applicable in the recipient country
may be charged.
For other regions in the world, other regulations apply. You should inquire about these
in your country. Often, however, no fees are charged for small orders.
Fake items
It’s become extremely difficult to buy cheap memory cards and sticks on the internet,
because (sorry to be so blunt) China has been flooding the world with fake memory
cards and sticks for several years. If you look for a larger memory device (over 100 GB)
on AliExpress or Ebay, and you buy the cheapest one, you’ll most certainly get a
worthless, fake device. Yet, they will still have seemingly good ratings. How is this
possible?
It’s a very deceitful scam, which the buyer will not even notice. The cards contain only
slow, low-quality memory chips of 8, 16, or max. 32 GB, but will appear to show up
having many multiples of this. When the buyer tests the card by loading a few
gigabytes onto it and then checking it, he’ll be quite satisfied, because the card appears
to work fine. At most, it may be noticeable that the card is a bit slow. When you fill the
card with data, it still seems fine, as the card will let you save as much data on it as it
pretends to hold.
But, here’s what buyers don’t expect: Only a few gigabytes will exist at a time. The
remaining data will contain only garbage, will be overwritten, or will simply be
dispensed with completely. However, this is only noticed after months. You find that the
data is gone, but still you are not suspecting any fraud; you’re simply disappointed that
the device has failed so quickly. Often, you may simply reformat the card, do some
quick tests, and again it will seem like the card is working fine again, only to lose your
data later again.
32
Unlike Ebay and AliExpress, Amazon managed to steer clear of such fake cards for a
long time. However, since 2021, one can find many fake products there. When using
search terms such as “sd 1024 gb,” one occasionally finds hundreds of fake cards and
very few genuine ones. Although Amazon has been informed of this many times, it took
months until these offers were removed. In the interim, many more fakes appear.
Therefore, I strongly recommend thorough testing of memory cards and USB memory
sticks immediately after purchase. A small freeware program with an amusing name,
“h2testw,” is your best choice. With it, you can write to and verify the entire available
(free) memory area on any storage medium, with just a few clicks.
False information
False information is often found in
online offers of any kind. A particularly
egregious example was with solder,
which was offered as a 50 gram roll. It
seemed a bit light to me, so I weighed
it, and, lo and behold, it came to
31 grams, including the dispenser. Now
I wanted to know exactly how much
Fig. 3.5: Scam solder dispenser roll
the solder weighed, so I unwound the
entire roll. Conclusion: a double-walled scam dispenser roll weighing in at 20 g and only
11.5 g (instead of 50 g) of solder. Later, it turned out that the solder wasn’t even up to
specification – it wasn’t usable at all.
It's better not to buy solder directly from China also for another reason: It’s a relatively
heavy product, so shipping is expensive; local dealers will usually be cheaper overall.
The same applies to heavy cables and the like.
False promises
When contacting sellers about products that aren’t in order or don’t match their
descriptions, many sellers (especially on AliExpress) react in an over-friendly way,
saying that they’re terribly sorry and that they will do everything possible to find a
satisfactory solution. In reality, they don’t budge an inch and will not refund a cent
voluntarily.
When you subsequently file a dispute, they tell you how damaging this is to them as a
merchant and implore you to cancel the dispute, after which you will get your refund.
Instead, of course, you get nothing and lose your buyer protection instead. There really
are sellers who make the same promise ten times without ever honoring their word.
33
Of course, not all sellers are the same. I have specifically pointed out the dangers and
warned against the bad actors, but you should not get the wrong impression. In most
cases, you get good products at very reasonable prices, and I am very satisfied with the
vast majority of my purchases.
For this reason, I recommend that the reader takes a quick look at the rest of the
chapters and projects now in order to the components for everything you’ll want to
build in the next few months. With the prices on AliExpress, it doesn’t matter if you use
a particular sensor or not, when you’ve paid a few cents or a euro for them. Some
dealers will offer many of the small parts that we need, without shipping costs rising
sharply with each additional part. In extreme cases, you can get a hundred different
parts delivered for the same price as a hundred pieces of the same part.
So, for now, you need to have a basic set of tools, consumables, and parts so that we
can get started with the upcoming topics and projects.
There is, however, a simple DIY solution – a converted soldering iron, which we can
switch between half- and full-power when necessary, with the help of a switch and a
34
simple diode. That's very simple, but we are working with wires that will later have
mains voltage. So one should already have some experience. Everything must
necessarily be well isolated!
It's best to use a simple soldering iron, preferably a small one with enough power, for
example 40 watts. Then, we need an additional power cord (3-wire with electrical plug
to plug into the mains). Simply cut the power plug off the soldering iron. The cable
remains attached to it and is connected, as in Fig. 3.6.1a, via the components to the
new power cable.
When switched to the low-power position, only a half-wave of the AC power will flow
through the diode (1N4007), thus halving the power. In the high-power position, the
diode is bridged by the switch.
With a three-position switch (0, I, and II below), one may switch the soldering iron
between high and low power, as well as turn it off completely. Fig. 3.6.1b shows how
this is done.
Warning: The wires must, of course, be well-insulated, so that you cannot touch any
of the contacts. After all, we are dealing with mains voltage here.
35
Additional tools
We’ll also need side cutters and a wire stripper. Fig. 3.6.1c shows two cheap options
from China, which I like to work with. A small pair of needle-nosed pliers (possibly
angled at the front), and a cutting knife are also useful. For components with screw
contacts, of course, you’ll need a screwdriver.
A hot glue gun is also very helpful, e.g. to affix wires and components, as long as they
don’t get too hot. Larger transistors and resistors, which heat up a lot during use, will of
course not hold using hot glue.
Be careful with the very cheap offerings from China. These small hot glue guns, at
about 3 euro, can supposedly be operated at anything from 100 to 240 volts, but in
reality don’t really like mains voltages over 200 volts, and may surprise you with a
spontaneous explosion, usually the moment you switch them on.
Serial adapters
We mustn’t forget the serial adapter, which I’ve already described in Chapter 2. I
recommend the universal adapter cable in section 2.1.1 on page 22. You’ll also find the
detailed component list there. The converter should have a DTR connection. A single
adapter suffices, but at the price (under €2 from China), it certainly doesn’t hurt to
have a spare available.
36
In other cases, the loads we want to drive (e.g. motors, power LEDs, etc.) determine
the current and voltage required of the power supply.
As far as current goes, all of the components must, of course, not consume more than
the power supply can deliver – the board itself consumes just a few milliamperes, which
is hardly significant. If the power supply can deliver more current than is required, that
is not a problem. Novices often worry that if a supply that delivers more current than is
required, it can break components, but only as much current will flow as the circuit
requires. It’s too high a voltage that’s dangerous.
Fig. 3.6.3a: USB power supply 5 V max. 1 A Fig. 3.6.3b: Power supply 5 V max. 2 A
37
In the end, it doesn’t matter whether you use a plug-in power supply, a power supply
module, or a power supply board. On AliExpress, a few dealers sell power supply boards
such as the one in Fig. 3.6.3b very cheaply. They come in different sizes and different
voltage and current ratings. What they all have in common are the short cables at their
inputs and outputs. Nonetheless, they’re new and very high quality, all short-circuit
protected and have some SMD components on the undersides of the boards.
Presumably, they’re from some over-production and may have been removed from new
products. I happily use such power supplies and have never had any trouble with them.
Of course, you have to consider that mains voltage is applied to the input lines – in
most countries about 230 volts. Caution must be exercised when using the bare power
supply boards, as parts of the board are under mains voltage. If you don’t want to
handle mains voltage at all, you can use such a power supply with closed enclosed and
a fixed or plug-in power cord:
Fig. 3.6.3d: 12 Volt power supply, max. 5 A (2 A version also available, for example)
38
DC connectors with an inner diameter of 2.1 mm and a 5.5 mm outer diameter are
common. As shown in the picture, there are adapters with sockets on one side and
screw terminals or cable sockets on the device side.
If you want to build portable, mobile projects using the 8 MHz Pro Mini or LilyPad
boards, lithium-ion rechargeable batteries are the best choice. Alternatively, you could
connect three regular 1.5 V disposable batteries (e.g. coin cells or AAs) in series. We
then need a battery holder for the three batteries. It’s also possible to use one of the
rarely-used 4.5 V flat batteries (type 3LR12). I generally recommend alkaline batteries.
Zinc-carbon cells have a much lower capacity, so they go flat much quicker.
Lithium-ion rechargeables
Of course, rechargeable lithium-ion batteries are preferable. They’re available in many
conceivable sizes and designs. Best suited are the 18650 cylindrical cells, or the rectan-
gular lithium-polymer cells, which are available any many different capacities and di-
mensions. More details about lithium-ion batteries and the handling thereof can be
found in Chapter 12. (We’re only covering shopping tips here.)
39
40
I recommend the version with the protection circuit. It costs just a few cents more, and
everything is integrated – charge controller and battery protection. To find these parts,
it’s best to search AliExpress for “USB lithium.”
More detailed information for both versions can be found in section 12.5 on page 264.
Resistors
Standard resistors are usually rated at ¼-watt, and they’re available in countless
different values in metal film as well as the somewhat cheaper carbon versions. They’re
usually available cheaply from local electronic stores or mail order companies. If you
have time to wait, you can also order them very cheaply from China. On these
sometimes the connection leads are a bit thinner, but that is not of much consequence.
Example: 1, 1.2, 1.5, 1.8, 2.2, 2.7, 3.3, 3.9, 4.7, 5.6, 6.8, 8.2, 10
After that, the series continues with 12, 15, 18, and so on. All in all, we make use of
values from 22 Ω to 1 MΩ, but not all of them. The table that follows on the next page
shows which resistors are used in the book.
41
12 Ω 15 Ω 18 Ω 22 Ω 27 Ω 33 Ω 39 Ω 47 Ω 56 Ω 68 Ω 82 Ω 100 Ω
120 Ω 150 Ω 180 Ω 220 Ω 270 Ω 330 Ω 390 Ω 470 Ω 560 Ω 680 Ω 820 Ω 1 kΩ
1.2 kΩ 1.5 kΩ 1.8 kΩ 2.2 kΩ 2.7 kΩ 3.3 kΩ 3.9 kΩ 4.7 kΩ 5.6 kΩ 6.8 kΩ 8.2 kΩ 10 kΩ
12 kΩ 15 kΩ 18 kΩ 22 kΩ 27 kΩ 33 kΩ 39 kΩ 47 kΩ 56 kΩ 68 kΩ 82 kΩ 100kΩ
120kΩ 150kΩ 180kΩ 220kΩ 270kΩ 330kΩ 390kΩ 470kΩ 560kΩ 680kΩ 820kΩ 1 MΩ
Only the values printed in black are necessary for our projects. The orange values are
needed for special settings, for example a voltmeter with a certain range of
measurement. The values in gray, on the other hand, do not appear in the book at all.
For current capacity, ¼ watt is sufficient. The tolerance should not be more than 1% (at
least for resistors that serve as voltage dividers for measuring). Incidentally, there are
resistor kits available that contain a wide range of E12 values. This is worthwhile,
especially if you want to design your own projects.
In addition, we need a few very-low-Ohm resistors that can handle more current. These
are used for measuring current. It’s recommended to have a couple rated at 1 Ω / 2 W
and a couple at 0.47 Ω / 2 W on hand. In this way, we can measure currents up to
1 A, and even a little higher. A very detailed list for many current ranges can be found
in section 6.2.4 on page 78.
Capacitors
Here, we need to distinguish between two different types: film capacitors and
electrolytic capacitors. Electrolytic capacitors are typically used for larger capacities and
it’s important to observe polarity when using them (i.e. the plus and minus connections
must never be reversed). Again, I’ve compiled a brief list of the most important
capacitors that you should have a few of in stock.
We get by here with very few types, each of which differ in capacity by a factor of 10.
The two gray values don’t even appear in this book, but there’s no harm in buying a few
of them as well. The maximum voltage rating is generous – more than we use. This
provides some safety and also reduces leakage current, which is very relevant to
battery-powered devices that need to run for a long time.
42
I recommend having a few LEDs of each primary color on hand, at least for the 5 mm
standard 20 or 30 mA LEDs. Similar ones are also available with 3 mm diameter.
Transistors
A very good transistor for switching loads of up to 800 mA at 45 volts
maximum is the BC337-40. As an electronics hobbyist, you should
always have some of these in stock. If you need to switch higher
currents, up to a maximum of 4 A, the BD435 is a good choice. With
special MOSFETs such as the NTD4906N or the IRLR8726PbF, even
higher currents can be switched easily. Detailed information about all of
these transistors can be found in section 7.2 on page 99.
43
Buzzer
The design shown here is quite handy when used with the Pro Mini (and similar
Arduinos), because it’s available as a 5-volt version and its power consumption is low
enough that it can be driven directly from an Arduino output (i.e. without an additional
transistor).
Such buzzers are very useful for acknowledging, for
example, the press of a button, with a short beep.
Simple information can also be relayed, such as by
varying duration and number of beeps. Even when
running on 3.3 V (lithium-ion battery voltage), the
5 V version works very well, even if not quite as loud.
Jumper wires
To connect individual pins on the pin headers (e.g.
from the Pro Mini board to other components),
these jumper wires are the simplest solution. You
can separate these jumper wires individually or in
groups. They’re available in various lengths and
configurations, e.g. with male pins instead of female
Fig. 3.6.4d: Jumper wires
sockets at one or both sides.
In principle, we could cut the wires in the middle and then solder them on, so that the
connection can only be plugged and unplugged on one end of the cable. However, since
copper is so costly, manufacturers also tend to use wires made of an aluminum alloy,
which doesn’t accept solder.
For permanent assemblies, however, soldered wires are better, and, if we want to make
components pluggable, multi-pin connectors (such as described in section 4.6) are more
stable and better than such single-pin connectors. There is also no problem with
soldering, as we use stranded copper wire.
44
Multimeter
The DT-830 is probably the world’s best-selling multimeter, and has
been for decades. It’s small, handy, relatively simple in design, and
you can get one for under 5 euro. No, that’s not a joke! And, if you
want to pay only half that, you can pool resources with friends and
place a collective order. That wasn’t a joke either – think of
vocational training classes, for example.
Infrared thermometer
If you occasionally work with power transistors or with ICs that switch larger power,
you’re probably aware of the guesswork around how hot these components actually get
– whether you need a (larger) heat sink, for example, or whether you could handle
even more current. In such cases, a non-contact infrared infrared thermometer is very
helpful.
When purchasing, you have to be careful that you don’t get a version intended as a
clinical thermometer, as these typically measure only a small range of temperatures.
Best is to search for “mini infrared thermometer” on AliExpress.
45
Vernier calipers
The simplest Vernier calipers are analog and
made of plastic. There are now also inexpen-
sive digital ones with displays. The simplest of
these are also made of plastic, but for
10 euro, directly from China, you can get a
high-quality digital electronic caliper made of
stainless steel, with a resolution of a
hundredth of a millimeter. Fig. 3.6.5c: Digital electronic caliper
46
You often have to deal with loose wires and contact problems. Wires that are not quite
straight anymore or have solder on them are often difficult to connect, and easily get
stuck. This makes it difficult to pull them out again and it wears out the contacts.
In addition, such a plugged circuit is not as suitable as a permanent solution, but only
as a test setup. Finally, you want to reuse your breadboard for other circuits, and any
shaking or vibration can easily impair the circuit’s function. Soldered connections are
better for permanent builds, in any case.
47
48
4.4 Perfboards
For little cost, you can get all kinds of universal boards with 2.54 mm grid-spacing.
They’re available with with single solder holes, strips, or other patterns. In principle,
any circuit can be built with them. In practice, however, it is usually a bit tedious to
connect or cut the necessary contacts, as and when you need them. Point-to-point
construction is easier. Nevertheless, I’d briefly like to cover the most common boards:
Hole matrix
These boards have only a single solder joint
per hole. The required connections (which
would usually be tracks on regular printed cir-
cuit boards) must be done using wires of
shorter or longer lengths on the underside of
the board. Where wires have to cross, it’s
best to lay a wire bridge on the top. For more
distant connections, we can take an insulated
wire, lay it on the top side of the board and
solder it through the neighboring holes. Fig. 4.4a: Hole matrix board
You can, of course, do all of this, but it’s not necessary. In my opinion, the hole matrix
board is the most tedious way to build a circuit.
49
Stripboard
On stripboard, the individual solder holes are
all connected together in either a horizontal or
a vertical direction, forming continuous strips.
This makes it easier to build circuits if the
components are arranged cleverly. You basi-
cally have all the connections you need at the
outset.
All in all, I think stripboard is more practical than hole matrix boards. However, con-
struction is not really easy.
50
In “Test and charging station for lithium-ion batteries” in section 8.5 on page 127 , I
have created a PCB layout that also works for “Regulated current source” in section 8.6.
This board can even be used for the simple constant-current source in section 8.4. For
the rest of our circuits, no printed circuit board is necessary. For example, we can solder
a multi-digit 7-segment display directly to the Pro Mini, as described on page 237. In
other situations, often only a single sensor has to be connected, or the construction is
so unique that it would be difficult to do on a uniform circuit board. For this, I
recommend point-to-point construction (possibly with a few thumbtacks).
In the past, electronics hobbyists often made boards themselves – exposed them
through foil, developed them, etched, and drilled them. Today, fortunately, there are
companies that offer the service at a reasonable price. For this reason, the downloads
for this book contain not only the program sketches, but also the circuit boards in
Gerber as well as bitmap formats. The downloads are available for free at
elektor.com/20243
The Gerber data consists of several files, each representing a different layer of the
board (traces, holes, labeling, etc.). They contain the vector information, and can be
opened with a text editor, although there’s not really any point in doing that.
On the internet, there are many companies from whom you can get some single boards
made from the Gerber files (which should be enclosed in a zip file) – allpcb.com and
jlcpcb.com come to mind.
It can also be useful to search the title of this book on Ebay, or use search terms such
as “arduino charging station board.”
51
For larger currents (over 100 mA), I use more than one pin (at least 2 per Ampere) to
be on the safe side. These are connected to each other, as a single pin might not ensure
proper contact, and may heat up excessively. For simple control signals, or for small
LEDs driven directly by the Arduino’s output, however, a single pin is always sufficient.
Coded connections
For these sorts of pin header connections, there’s a simple
way to ensure that the plug cannot be inserted the wrong
way around, and that two plugs can’t be mixed up if you
Fig. 4.6b:
have identical ones of the same length. Your plug and
Coded connection
socket need to have one extra pin than is required. The
extra pin (doesn’t matter which one it is, just not the middle one) is then cut off with a
side cutter. Don’t let the pin fly away when you do this, because we still need it. Insert
it into the corresponding hole on the female header, exactly where it would normally
have plugged in, i.e. the connection that’s now missing on the male side, and leave it
there. Now, it’s no longer possible to insert the plug incorrectly, because if you did, the
intact pin from the header would hit the pin that’s already occupying the hole.
52
Chapter 5 Programming
Ordered all the necessary and interesting components? Now, while we wait for our
package (from China), we can install the free Arduino-Software on our PC and get
familiar with it.
We’ll often need to use the magnifying glass icon on the far right, as this opens Serial
Monitor – a text window that shows us the data the Arduino is sending when we make
use of serial output in our programs. It’s important that the transmission rate selected
there matches the transmission rate in our sketch. If not, only garbage (or nothing at
all) will be displayed.
53
First, I’d like to explain the basic structure of an Arduino sketch. These syntax
explanations will be identifiable by the light blue boxes in this book.
The setup() function is executed only once, at the beginning (after switch-on or
reset). After that, the loop() function is repeated endlessly. The curly braces are used
to group several instructions (denoted here only as […]) into a block. The content of
such a block is usually indented, using the Tab key. The Arduino IDE automatically
indents code on the next line after you enter an open brace { and press Enter.
All of this together makes a function. For beginners it may be irritating that only for
the curly brackets separate lines are used, but this serves the clarity, whereas many
programmers like to write the opening bracket still at the end of the previous line.
By the way, the double slash “//” is used to mark a comment. From there on, the rest
of the line is not interpreted as part of the program code.
54
At the beginning here, we set Pin 13 as an output. This is the pin that’s connected to
the small LED on the board.
(By the way, all of the sketches in this book are divided into individual program
sections and explanations, separated by this zigzag line. The explanation always refers
to the program section above.)
Here, the board’s LED is turned on, and, after 100 milliseconds, is turned off. After
another 100 ms, the fun begins all over again. This creates a fast blinking effect. A
detailed explanation of the pinMode(), digitalWrite(), and delay() functions will follow
later.
If you like, you may enter other time delays into the sketch and try them out. The
blinking frequency is easily derived from the two delay times:
5 Hz was chosen here in order to be quite fast, as new boards usually come with a
slower blink sketch already installed with the Bootloader. (If the new board doesn’t
blink, it may not have a bootloader installed.)
55
Under Menu→Board, select “Arduino Pro or Pro Mini.” Then, below the Board option, we
see the “Processor:” menu item, where we must select the correct version. Depending
on the USB adapter used, we may also need to use the Port menu option to select the
correct port. We should try this if uploading our sketch fails.
56
57
58
The digital inputs are always read as “LOW” if their voltage is close to ground
(somewhere between 0 and 1 volts). Voltages close to VCC (i.e, at a 5-volt operating
voltage, about 4 to 5 volts) are interpreted as “HIGH.” In the mid-range (e.g. at
2 volts), on the other hand, the result is undefined.
To read an input pin with a program and do something with it, we need some new
language elements. The second light blue box on the page after the next one describes
the actual reading of the pin. If you are already familiar with the programming
language, you can safely skip that pages.
59
Syntax: Variables
In principle, variables are simply storage areas that are allocated by giving them
a name.
Variable names can also be much longer and contain upper and lower case letters, as
well as the underscore. Even digits can be included in the name, but not as the first
character. These are the different variable types:
bool
Or also “boolean,” a single bit that may only be “0” or “1,” or rather “false” or “true.”
You could also say “LOW” or “HIGH.” These terms are all interchangeable.
byte
A byte is, as the name suggests, a simple byte, consisting of 8 bits. It can hold values
from 0 to 255.
char
In principle, also a byte, but it’s used to hold characters, i.e. those that use ASCII
codes. For example, ‘A’ is 65. Instead of 0 to 255, the range for char is -128 to 127
(on the Arduino platform, at least).
int
Consists of 2 bytes, represents integers between -32,768 and 32,767
unsigned int
Like “int,” integers, but the range is from 0 to 65,535
long
Integer consisting of 4 bytes, ranges from -2,147,483,648 to 2,147,483,647
unsigned long
Like “long,” an integer, but from 0 to 4,294,967,295
float
Can be used for numbers with decimal fractional parts, as well as very small or very
large numbers, but only to 6 or 7 digits of precision (no matter their distance before
or after the decimal point).
Variables can be defined right at the outset (before the setup() function). These will
always be valid for the entire program. You can also define variables within functions
or loops, i.e. within curly braces {}. Then, they are only valid within these and are
redefined on each pass.
60
pinMode(2, INPUT);
n = digitalRead(2);
This small example sets Pin 2 as an input. Then the present level (“LOW” or “HIGH,”
resp. 0 or 1) is read and the result is stored in the variable “n.”
pinMode(2, INPUT_PULLUP);
n = digitalRead(2);
Yes, you’ve guessed it: Here, the internal pull-up resistor is also activated. In this
way, you’ll always measure a “HIGH” signal when nothing is connected to the pin.
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
This small example sets Pin 13 (the pin that already has an LED attached to it on the
board) first as an output, and then to the “HIGH” level. The LED lights up. Instead of
“HIGH,” you could also write “true” or “1” or insert a variable, which then would
determine whether the LED lights.
61
if (n == 7)
{
n = 0;
}
else
{
n++;
}
This is a typical example of a condition. While the single equal sign is used for
assignment, the double "==" is used for comparison. The result of a comparison is
always “true” or “false,” i.e. a “bool” value that determines whether the following part
is executed or not. Therefore, instead of a comparison, you can also use a single
value - e.g. a variable. Everything that is not 0 or “false” is considered to be “true.”
Optionally, you can add the “else” construction after it, which is executed if the
condition is “false.”
== equal
!= unequal
> greater than
>= greater than or equal to
< less than
<= less than or equal to
It is also possible to link several comparisons with “&&” or “||.” “&&” denotes the AND
operation. The overall statement is only true if both comparisons (i.e. before and
after the “&&”) are true. With the “OR” operation, “||,” at least one condition must be
true.
The braces {} enclose the program part for which the condition applies, similar to
how such braces enclose a function. If (as in the example above) there’s only a single
statement in there, we could also omit the braces.
62
The while loop is basically the same as an if condition, but with the difference that the
content in the braces is not only executed once, but again and again, as long as the
condition is fulfilled. In this small (meaningless) example, “n” is reduced by 1 over
and over again until it’s no longer greater than 10. If “n” was already smaller at the
beginning, the content is not executed at all.
do
{
n--;
}
while (n >10);
This is the do-while loop, a variant of the while loop. Here, the content is executed
first and then repeated, depending on the condition. The only difference is that the
content is always executed at least once and only afterwards the condition is
checked. Here, a semicolon comes after the while condition.
Further information about functions and syntax can always be found on the Arduino
website at: arduino.cc/reference/en
Pushbutton switch
With a simple little program, we can now try out the use of the inputs. The program
turns a pushbutton into a switch. A pushbutton remains switched on only as long as you
press it, much like a doorbell. Here we can switch on the LED on the board with a
pushbutton (or in the simplest case with wire to ground, which is briefly touched to the
input pin). If you release the button, the LED remains on. Only if you press the button
again (i.e. if the input pin is grounded again), the LED switches off again… and so on.
63
Here, first the input and output pins are defined. Of course you can also change these.
The “state” variable stores the respective LED switching state.
void setup()
{
pinMode(input_pin, INPUT_PULLUP); // input with pull-up resistor
In setup(), the pins are set as an input and an output and the LED is switched off.
void loop()
{
if (!digitalRead(input_pin)) // when button is pressed
{
state = !state; // other state ("!" means "not" or "opposite")
digitalWrite(led_pin, state); // switch output (LED)
delay(100); // wait 100 milliseconds
while (!digitalRead(input_pin)) // as long as button is pressed
{
// nothing (wait until button is no longer pressed)
}
delay(100); // wait 100 milliseconds
}
}
In loop(), we check if the pushbutton is pressed. The “if” evaluation does not have a
comparison here, but only an expression that can be true or false, just as with a
comparison. If the key is pressed, “digitalRead()” returns false, because we are not
switching to positive, but to ground. The “!” in front of it inverts the signal and now
returns “true” if the key is pressed. Only then will the content be executed.
In this case, “state” (with the help of the exclamation mark "!") is now toggled. True
becomes false and vice versa. The while loop then queries the input pin until the
button is no longer depressed. The two delays help to debounce the button press.
64
The script can of course be extended with further program parts in the loop. However,
this simple version has a disadvantage: As long as the button is pressed, the program
hangs within the empty while loop and cannot do anything else. To prevent this, we
need an additional status variable for the button instead of the while loop, in which we
store whether the button is currently pressed or not. Only if this variable doesn’t match
the newly-read signal (and furthermore only when it’s either pressed or released) the
device will be switched – on if it is currently off, and off if it is currently on. The changed
state (pressing and releasing) must then of course also be saved to the status variable
as true or false each time. In the very last sketch on page 324 we have something
similar – also with debouncing. The variable that stores the button state is called
“pressed” there.
Reference voltage
Each measurement is relative to a reference voltage, which defines the measuring
range. While a measured value of 0 basically corresponds to 0 volts (i.e. GND or
ground), the reference voltage corresponds to the highest voltage that can be
measured, or the highest value plus 1, i.e. 1024, to be precise. The reference voltage
thus determines the measurement range.
VCC as reference
By default, the operating voltage, VCC, serves as a reference. Since the Pro Mini boards
have a high quality voltage regulator, which regulates VCC quite accurately to 5 V (or
3.3 V for the 8 MHz version), you can measure voltages of up to 5 V (or up to 3.3 V)
quite accurately. But, the voltage supply at the RAW pin should be at least 1 V higher
than VCC, so that the voltage regulator can work properly.
The situation is completely different with battery-powered devices. There, the battery
voltage is usually used directly as VCC, for example with the LilyPad. Thus, VCC is not
very useful as a reference, because the battery voltage is anything but constant.
65
Internal reference
In addition, there is an internal reference voltage of 1.1 volts, which is very consistent,
but unfortunately not very accurate. What seems like a contradiction at first sight, can
be explained easily: From board to board, the reference voltage can vary significantly.
On one board, for example, it may be 1.03 volts, and on the next, 1.16 volts.
Deviations of ±10 percent are possible. The reference voltage on the board, despite its
deviation, is, however, very constant, even if e.g. the temperature or the operating
voltage, VCC, changes. Thus, a one-time calibration is always necessary here if you
want to measure accurately, which you can do as soon as you know the true reference
voltage.
External reference
The microcontroller also has a connector to which we can apply an external reference
voltage. Unfortunately, this connection is only available on the Uno and Nano boards as
the “AREF” pin. On the Pro Mini, we don’t have this connector. Usually we have to limit
ourselves to 1.1 volt or VCC as reference, but this is quite enough. In this book, we
don't use the external reference anyway.
n = analogRead(A0);
In this example, the voltage at A0 is measured and the result is assigned to the
variable “n,” which must be at least in “int” or “unsigned int” format, because a 10-bit
value is output, which can range from 0 to 1023. A simple byte would overflow at
values above 255.
We should also note that measured values cannot simply be retrieved directly (like
digital values with "digitalRead()"), because calling “analogRead()” first initiates the
measurement, and this takes over 100 microseconds each time.
analogReference(INTERNAL);
This switches the measurement range to the internal reference voltage of approx.
1.1 V. Thus, small voltages can be measured independently of the operating voltage.
Further possible specifications are “DEFAULT” for the standard range (VCC) and
“EXTERNAL” for the “AREF” connection. However, the latter is available only on the
Uno and Nano boards.
66
Since there is no resistor connected to ground here, the entire measurement input is
extremely high impedance. If nothing is connected, the measurement can result in
anything from 0 to the maximum value. To prevent this, an additional 1 MΩ resistor can
be connected from the measurement input to ground. The input resistance is then (just
under) 1 MΩ.
The capacitor filters out interference and thus provides a more stable measurement
result. The larger the capacitance, the more stable the measurement result. With
smaller capacitors, on the other hand, the measurement reacts more quickly to
fluctuations at the input. 100 nF is a good value for most applications. But, if you need
1000 or more measurements per second, it is better to reduce it to 10 nF.
With a simple program, we can now take measurements and convert the measured
values into volts or millivolts. Before that, let’s take a quick look at a few new language
elements that are used in the sketch:
Such lines at the very beginning of the program are not the usual variable syntax,
because instead of the "=," there is only a space. Likewise, there’s no semicolon at
the end. When compiling the program, the text “measure_pin” is now replaced
everywhere by the value after the space.
It is also possible (often even recommended) to define constants like variables but
with the word “const” prefixed. Example: const byte measure_pin = 14;
67
This initializes serial transmission via the UART interface. This is the interface that’s
also used to transfer the program to the board, basically an RS-232 interface but with
a 5 V- (or 3.3 V-) logic level. The number indicates the speed of the upcoming
transfers. 38400 is usually a good choice for this. The transfer is then quite fast, but
also not so fast that the board, the PC, and common USB adapters can’t keep up.
Serial.print("Hello");
This is how text is be transmitted via the interface. In the Arduino IDE you can use
the magnifying glass icon in the upper right corner to open Serial Monitor, a window
that shows the output from the Arduino board.
Serial.print(x, 3);
Without the quotation marks, variables (in this case, “x”) can be output as text. The
comma and the number after it are optional – the number allows us to specify how
many decimal places should be output, which, of course only makes sense for values
in floating-point format. The default value is 2. This means that, if you don’t specify
it, “float” values are output with two decimal places.
If you use “println(),” a line break is sent out after the output, so that the next output
starts on a new line. We can also create line breaks within the text using “\n.”
68
Calculation pitfalls
For calculations (e.g. "a * b"), the larger data type of the two variables is always used.
So if “a” is a byte and “b” is an integer value (int), the calculation is done on an integer
and the result is an integer value. But, we get an overflow and therefore a completely
incorrect result if the product, “a * b” (or, in case of an addition, the sum) does not fit
into an integer type.
Likewise, “a * b / c” produces an incorrect result, even if the result becomes very small
from the division and can be assigned to an integer type, because the overflow already
took place before the division.
In most cases, “a * (b / c)” is also not a good solution. This avoids the overflow, but,
since we aren’t calculating with floating-point values, everything after the decimal point
is discarded when dividing. The overall result becomes very inaccurate. For example, if
“b” is smaller than “c,” the result will always be 0, no matter how big “a” is.
Better to use “long(a) * b / c.” Now, “a” is interpreted as a long value (with 4 bytes or
32 bits), and there is no overflow anymore, because the multiplication is now also done
on a long type. The result can still be stored as an integer type again, as long as “c” is
big enough so that the result is small enough to fit into the int type again.
Of course, you could also use floating-point variables and calculate everything using the
float type. But such calculations are always slower, and, in this example, only
recommended if you really need the result to have decimal places.
b = round(a);
Here, the decimal variable “a” is rounded to the nearest integer and assigned to “b.”
This is the so-called “commercial rounding.” After the decimal point, everything from
5 up is rounded up. Besides “round(),” there are still:
69
#define measure_pin A0
#define vcc 5.00 // operating voltage in volts (calibrate!)
Here two constants are first defined “measure_pin” is "A0,” and as “vcc” the operating
voltage (VCC) must be defined as precisely as possible. Then the required variables are
defined, as explained in the comments.
void setup()
{
pinMode(measure_pin, INPUT); // set measuring pin as input
Serial.begin(38400); // enable serial transmission
conversion_factor = (float(vcc) / 1024); // conversion factor to volt
}
In setup(), the measuring pin is set as an input, serial transmission (to output the
measured value later) is initiated and the conversion factor for calculating the voltage
is determined. In principle, the conversion factor says that a measured value of 1024
corresponds to the operating voltage (VCC). The actual maximum value of 1023 is thus
very slightly below this.
void loop()
{
counter++; // count
measurements += analogRead(measure_pin); // add new measurement
if (counter == amount) // if amount is reached
{
voltage = measurements * conversion_factor / amount; // calculate voltage
70
}
}
In the loop, the counter is incremented by 1 each time (starting at original 0), an
analog measurement is performed and the result is added to variable “measurements.”
Only when the counter has reached the number "amount” is the voltage is calculated,
output, and the old value is deleted and the counter is reset.
The program now continuously measures the voltage at A0 and calculates the average
voltage after every 10,000 measurements and outputs it serially. With Serial Monitor in
the Arduino IDE you can view the voltage as an output.
Of course, you could also perform only one measurement and calculate and output the
voltage using it. The remaining time (until you want another measurement) could be
bridged with a delay() function. But the result is more accurate and has a higher
resolution if you take many measurements, and, since the program has nothing better
to do anyway, we can use the time wisely this way.
Calibration
The calibration here is quite simple. We only have to measure operating voltage VCC as
accurately as possible using a voltmeter or multimeter, and enter this above in the
second line of the program code as a reference. Of course, the modified program must
then be uploaded to the board. All this only makes sense if the voltage remains stable
permanently. If you use the voltage regulator on the board (with a higher supply
voltage at the RAW connector) this would be the case. If the voltage regulator is very
accurate, we can also do without the calibration and enter 5.00 (or 3.30) as voltage.
71
By the way, later in section 11.2.2 on page 244 there is an extended version with a
4-digit display – a complete voltmeter using only a few components.
Possible ranges
The input resistance results from the sum of the two resistors. If you need a particularly
high-impedance input that does not load the measured voltage, you can increase the
two resistors by a factor of 10 each. If the measurement still has to react quickly to
changes, the capacitance should also be reduced to one tenth (i.e. 10 nF). The
capacitor should ideally be installed as close as possible to the Arduino input.
Here, we can also output the measured voltage using a brief sketch:
72
Here, again, the constants are defined first. In addition to the reference voltage, the
two voltage divider resistors must also be specified exactly. Then, the required
variables are defined.
void setup()
{
pinMode(measure_pin, INPUT); // measuring pin as input
Serial.begin(38400); // enable serial transmission
analogReference(INTERNAL); // use internal reference for ADC
conversion_factor = (float(reference) / 1024); // conversion factor to mV
conversion_factor *= 1 + (r1_resistor / gnd_resistor); // with voltage divider
}
In setup(), the measuring pin is defined as an input, serial transmission is set up (to
output the measured value later) and the internal reference voltage is set as the upper
limit of the measurement range. The conversion factor is then first calculated as it
would be with direct measurement (without the voltage divider). The voltage divider is
then taken into account in an additional line.
void loop()
{
counter++; // count
measurements += analogRead(measure_pin); // add new measurement
if (counter == amount) // if amount is reached
{
voltage = measurements * conversion_factor / amount; // voltage in mV
73
Just as with the direct measurement, you can also view the measured voltage in the
Arduino IDE’s Serial Monitor. Each displayed value is also averaged out of 10,000
measurements.
Calibration
Calibration is a bit more complicated here, because we now have to specify the internal
reference voltage in the program sketch instead of the more-easily-measured operating
voltage. By default, the approximate value of 1100 mV I specified. For calibration, we
have to measure a voltage that we know exactly. Depending on the measuring range,
we can use VCC or RAW by tying the measurement input pin to it. Then, we compare
the voltage measured with the voltage displayed (using an accurate multimeter) by the
serial monitor and we can calibrate:
Furthermore, we can use a resistor (e.g. 1 MΩ) to ground, which determines the input
resistance. Without this (dotted) resistor, the input is extremely high impedance
(approx. 100 MΩ).
The voltage divider on the right is not part of the actual circuit, but it might later be
useful for calibration by providing a suitable voltage.
For the program, we use the same sketch as in section 6.2.2 with voltage divider, only
now we omit this line:
This is the line that adds the voltage divider to the conversion factor. Instead of
removing the line completely, you can simply comment it out.
The slashes at the front turn the entire line into a comment. This way you can reuse it
later if needed. When testing our changes, we can duplicate the original line,
comment out the original, and then change the new one. We can also comment out
several lines at once, like this:
/*
These three lines
are now
just comments!
*/
Calibration
Here, too, we have to apply a calibration voltage that we know exactly. Since the
measuring range goes up to 1 volt, a voltage of between 0.5 and 1 volt is
recommended. If you don't have a suitable voltage source at hand, you can make a
voltage divider from two resistors to create one. Add a 1 kΩ resistor to ground and
another of 4.7 kΩ to VCC (5 volts). This gives us a (calculated) voltage of about
0.88 volts, which we can apply to the input and use for calibration. In addition, we use
an accurate multimeter to measure the actual voltage.
75
Again, “reference_old” is the reference voltage (1100) previously entered and used in
the sketch. The actual voltage is the voltage we know or measure using an accurate
multimeter, and we see the measured voltage in Serial Monitor. We now get the
corrected value for the reference voltage, enter it in the sketch and upload it to the
board. The value should be between 1000 and 1200, or something went wrong.
The internal reference voltage of 1.1 V is very suitable for the measurement, because
the measuring resistance (and thus also the voltage drop) will be very low. With a
reference voltage of VCC, e.g. 5 volts, no useful measurement of voltages in the
millivolt range would be possible.
#define current_pin A0
#define resistor 1.00 // measuring resistor (shunt)
#define reference 1100 // internal reference in mV (calibrate!)
76
First, the constants are defined. In addition to the reference voltage, the measuring
resistor must be specified exactly. Then come the variables. We now have “current”
instead of “voltage.” The rest remains the same.
void setup()
{
pinMode(current_pin, INPUT); // measuring pin as input
Serial.begin(38400); // enable serial transmission
analogReference(INTERNAL); // use internal reference for ADC
conversion_factor = (float(reference) / 1024) / resistor; // calculate factor
}
In setup(), the measuring pin is defined as an input, serial transmission is initiated (to
output the measurement later) and the measurement range is set to the internal
reference voltage. The conversion factor here is still divided by the measurement
resistor. Thus, we calculate the current instead of the voltage.
void loop()
{
counter++; // count
measurements += analogRead(current_pin); // add new measurement
if (counter == amount) // if amount is reached
{
current = measurements * conversion_factor / amount; // current in mA
77
Here, too, 10,000 measurements are made each time, from which the current is then
calculated and transmitted serially. In the Arduino IDE’s Serial Monitor, we can view the
measured current.
Possible ranges
(Almost any) other measuring range is possible. The table is a guide:
As with the voltage measurement, all resistors were calculated so that the specified
maximum current can be measured in any case. There is never more than 1 volt at the
resistor, because the reference voltage at a ±10% tolerance is between 1.0 and 1.2 V.
78
Calibration
There are ways we can calibrate here. On the one hand, we can measure the current
directly with an accurate multimeter, compare it with the displayed value and calibrate
it. On the other hand, we can also determine the exact voltage drop at the
measurement resistor, in the millivolt range, using a good multimeter, and calculate the
current from this. This may give us a more accurate value for the reference voltage.
(This is important if other measurements are also made.) If, on the other hand, we
measure the current, deviations in the measurement resistor are also taken into
account. Then the current measurement may be more accurate.
In this way, we determine the internal reference voltage from a current measurement
from the load. The current-measuring device is connected in series with the load. The
actual current is that which the measuring device (multimeter) displays.
“measured_current” means the current displayed by Serial Monitor, not to be confused
with the actual current from the multimeter.
In this way, we determine the internal reference voltage by means of the voltage drop
at the measurement resistor (R). The “actual_voltage” is the voltage measured with the
multimeter. The “measured_current” is the one displayed by Serial Monitor.
We enter the newly determined reference value (millivolts, rounded as an integer) into
the program and re-upload the sketch to the board. The value must be between 1000
and 1200. Otherwise, there’s an error somewhere.
79
Incidentally, the measurement is most accurate when the two resistors are approxi-
mately the same size. If, for example, if a resistance is to be measured that can lie in
the range of 1 to 10 kΩ, then 3.3 kΩ would be an ideal value for R1. This way, the
resistors are never more than a factor of 3.3 apart.
A relatively high-impedance NTC has the advantage that no significant current flows, so
it won’t heat up itself during operation and falsify the result. An additional 100 nF
capacitor from the analog input to ground can also minimize interference here and thus
smooth the measured value.
80
The sketch is largely self-explanatory. The measured value is also averaged out 10,000
times here and then processed further. The variable “ratio” indicates the ratio of the
resistors (rm/r1). The output distinguishes 3 ranges (MΩ, kΩ and Ω).
81
Swapping resistors
If (for whatever reason) the resistors are to be reversed, so that the measured resistor
is connected to the positive pole (instead of to ground, where R1 is then connected),
the variable “ratio” no longer returns rm/r1 but r1/rm. You then have to divide “r1” by
“ratio” in the sketch’s following line instead of multiplying – so replace the old
multiplication line with this one:
Calibration
Calibration is not necessary here if we use an accurate R1 with 1% tolerance, because
R1 is the only reference that influences the result. In case of an inaccurate R1 we can
also measure using a good multimeter and then enter the correct value in the sketch.
Of course, this only makes sense if the multimeter is more accurate than the original
specification. So if we use a resistor with 1% tolerance, and our multimeter has 2%
tolerance, then doing so makes no sense.
“HIGH” corresponds (almost) with operating voltage VCC. Depending on the load at the
terminal – if a current flows to ground – the voltage is slightly lower, for example
4.8 volts at a 5-volt operating voltage.
If we switch to the “LOW” level, the voltage changes to (almost) 0 V, and the reverse is
now true: Depending on what is connected to the pin – whether a current flows toward
the positive pole – the voltage is just above 0 volts.
What you can do with the output signal of such a pin, i.e. how to switch something with
it, is what the next big chapter is about:
82
The output lines of an Arduino board supply the operating voltage of the microcontroller
(i.e. usually 5 V or 3.3 V, or slightly less) in the HIGH state, and in 0 V in the LOW state
(or slightly more). The connections should not be loaded with more than 25 mA each.
The absolute maximum allowed is 40 mA, but it is better to stay well below that,
especially if we make use of multiple outputs under load.
If many outputs are used, the load on each line should be even lower. Depending on the
loads’ distribution, we should not exceed a total load of 100 or 200 mA. If we are
slightly above this, the microcontroller will not break down immediately, but we don't
have to torture it unnecessarily.
7.1 LEDs
Simple colored LEDs (red, yellow, green, blue, and white) e.g. in the conventional
design of 5 mm or the smaller 3 mm diameter can be operated without any issue on an
Arduino output. However, LEDs don’t tolerate 5 V directly.
83
Here we have used a target current of 13 mA for the LED and an LED voltage of 2 V,
which fits relatively well for red, yellow, and green LEDs. Thus we have an ideal value of
230 Ω. The next most suitable resistor (from the E12 series) is 220 Ω here.
With blue LEDs it looks a bit different. There, we have to do the math using about a 3 V
voltage drop at the LED, so we come to about 150 Ω.
R = (5 V – 3 V) / 0.013 A = 153.8 Ω
Often it becomes apparent during operation that the calculated resistance values are
not quite optimal, e.g. if the LEDs are too bright or not bright enough in daylight.
Sometimes you use several LEDs in different colors and then notice that one color looks
brighter or weaker than the others. Then you can correct the values. The lower the
ohmage, the brighter the LED. We shouldn’t go below a hundred ohms, as the current
becomes too high.
84
Blue LEDs are difficult to run on batteries, because the voltage difference goes to zero
when the battery is almost empty. Thus, the series resistor would also have to go to
zero (i.e. conduct fully) to maintain the brightness. With a full battery, on the other
hand, the series resistance is definitely required. Therefore, I recommend not using
blue LEDs in battery-powered devices. If you want to use them anyway, it’s best to use
100 Ω, but then you have to expect that blue LEDs will weaken a bit when the battery is
almost flat. This has its advantage: it can be quite practical, as you can recognize early
on that the battery needs to be charged soon.
Usually you would create an alternating flasher with two outputs (one per LED), as in
Fig. 7.1.1. Then you could also switch both LEDs on (or off) at the same time. This
would not be possible here. You could set the pin as an input (and thus deactivate it),
but then (depending on the operating voltage) both LEDs would light up weakly or not
at all.
85
We then place the printed plan on the board and press in the thumbtacks. The Arduino
(Pro Mini or LilyPad, depending on the version), goes in the middle, possibly over some
hot glue. Then the resistors are soldered on first, then the LEDs, and finally the wire
connections.
We need:
Depending on construction:
86
The 8 MHz board can be operated on 3 to 5.5 V (3 V to 4.2 V in battery operation). The
16 MHz version, on the other hand, operates with a fixed 5 V. The optimal series
resistors, depending on LED color and operating voltage, are shown in this table:
87
For white or blue LEDs, the operating voltage should be at least 3.5 V. Of course, you
can also use 330 Ω in general, and then you’d be on the safe side in any case, even if
the LEDs end up being not be quite as bright.
This version is easy to build, and especially suitable for battery operation. However, we
have to bend the 6-pin header (with the connected female header) away a little, so that
the USB adapter can still be plugged in. At the same time, we have to press the header
against the board, so that the solder connections don’t break off during bending.
88
This version is not as easy to solder because of the smaller contacts, but it can be
operated at a higher voltage (up to 9 volts) at the RAW input.
89
Before I present the effect programs for the LED board, I need to explain a few
programming basics. If you’re already familiar with these, you can skip this section.
Syntax: Arrays
Sometimes you need a whole array of similar variables that are sequentially
numbered. When creating such arrays, you simply put the required number in such
brackets after the variable name:
bool led[20];
byte pin[20] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1};
If you want to initialize the array with values for the individual indices, you can do so
in this way. The individual values are enclosed in braces, separated by commas.
Here a variable, “n,” is defined and set to 0. (If the variable already exists, leave out
the data type keyword, “byte”). Then the condition “n < 20” is checked. At first, this
will be the case, therefore the program block between the braces {} will be executed:
led[0] is set to HIGH. After that, “n++” is executed, so “n” is incremented by 1. Then,
the condition “n < 20” is checked again, and so on.
At some point, n will reach 19. led[19] is set to HIGH and “n” is increased again. At
this point, “n” is equal to 20, and the condition is no longer fulfilled. The loop is
exited. That makes 20 passes, with “n” going from 0 to 19.
90
delay(1000);
This simple instruction creates a delay of one second, because the specification is in
milliseconds. With the largest possible number (4,294,967,295 for unsigned long) the
Arduino would wait for almost 50 days.
delayMicroseconds(100);
This creates very short delays, because here the specification is not in milliseconds
but in microseconds. Here, only values up to 16,383 should be used.
In addition, the Arduino also has two system times that start at zero after power-up
(or reset):
micros() returns the elapsed time in microseconds in unsigned long format. But the
actual resolution is 4 times lower, so the value changes only 250 times per
millisecond in steps of 4 for the 16 MHz version, or steps of 8 on the 8 MHz version.
91
// Settings:
In this first part of the program, we first define the basic settings using 4 variables. In
the variable “amount” we first have to specify how many LEDs are be used in total.
“active” specifies whether the LEDs are lit up, using LOW or HIGH. Since we connected
the LEDs to VCC, not ground, LOW means switched on.
pin[20] is an array, where we have to specify the pins used, in order. For example, if
we only populated every fourth LED, then, starting with Pin 2, we’d have
“{2, 6, 10, 14, 18}”. By the way, it is recommended to always start at Pin 2, so that
Pins 0 and 1 are the last two, because these are used for serial transmission (TX and
RX) and should therefore only be used for the LEDs if you really need them.
Two more variables are defined here. led[20] is also an array that indicates the current
switching state of each individual LED. This is initially filled with zeros, since all LEDs
should be off at the beginning. The variable “n” is used for counting and later indicates
which LED we’re currently working with.
92
void setup()
{
for (n=0; n<amount; n++) // for all LEDs
{
pinMode(pin[n], OUTPUT); // set corresponding LED pin to output
digitalWrite(pin[n], led[n] == active); // switch output accordingly
}
}
The setup() function contains a for loop, which counts “n” from 0 to 19 (or 0 to LED
count-1). The individual pins (whose number is read from pin[n]) are first set as
outputs using pinMode(), and switched on or off in the next line. For this, the current
switching state of the respective LED, led[n], is compared with the “active” variable.
The result in each case is “true” or “false,” i.e. HIGH or LOW (the LEDs light up when
it’s LOW).
void loop()
{
n++; // count (1 more each pass)
if (n >= amount) // if total number of LEDs is reached
{
n = 0; // reset counter
}
led[n] = !led[n]; // toggle state entry (switch on/off)
digitalWrite(pin[n], led[n] == active); // switch output accordingly
delay(blink_time); // wait (as defined)
}
In the loop, “n” is increased by 1 with each pass. However, when the LED count (20) is
reached, “n” is reset to 0. This way, we only count from 0 to 19. Then the indexed LED
with the respective number is toggled in the array: led[n] = !led[n]; The exclamation
mark means “not,” which means that “true” becomes “false” and vice versa. Then, the
new (toggled) value is sent to the pin using digitalWrite() (as was done in setup()).
The last step is to wait using delay(), for as long as is specified in blink_time. The
specification is in milliseconds, so 1000 would be one second.
The complete sketch is also available for download (as are all the others) on the Elektor
website at elektor.com/20243
93
Example: n = random(amount);
If we replace the first half of the loop function (where "n" is specified) with this line,
then a random value from 0 to amount-1 is generated on each pass. This way the LEDs
will be switched completely randomly.
n = random(1, 21);
Here, the variable “n” is assigned a random value between 1 and 20. The first number
can also be omitted if it is 0: with random(0, 20) or simply random(20), we get
random numbers between 0 and 19.
Strictly speaking, random numbers are only seemingly random. In reality, it is a fixed
series of over 4 billion numbers generated by an algorithm. To avoid getting the same
sequence of numbers every time you turn the board on, you can use randomSeed():
randomSeed(analogRead(A7));
This defines the position (in the algorithm) for the next random number. If you use
the analog value of an unused input (as in this case), it will be something between 0
and 1023, which is relatively random.
94
// Settings:
Here again we have the same variables for the basic settings. What’s new is the
blinking pattern, which we can specify in the led[] array using 1s and 0s. The
“movement” variable specifies how this blinking pattern moves. Here, we could use -1
to change the running direction, for example.
There are further variables here that the program needs. led_new[] is (like the led[]
array) an additional array for the LEDs’ switching states, which is later used
temporarily when moving them. “n” is again for counting, and “pos” is the respective
shifted position of “n.”
void setup()
{
for (n=0; n<amount; n++)
{
pinMode(pin[n], OUTPUT); // set pin as output
}
}
In setup(), all LED pins are set to output in a for loop. The individual LEDs are not yet
switched here, because the loop() function will switch all of the LEDs at once.
95
void loop()If we change the “oversampling” value to 3, only a good 32 (instead of 100)
values per second will be recorded, which is still far more than enough than we need
for weather recordings. We could therefore also include the SD card functions from the
altimeter (section 13.8) and use it to open a text file and write a corresponding text
line into the file for each serial output as well. This way, we get the weather recording
as a text file.
So, you see, there are countless possibilities to what you can do, and the individual
chapters offer—besides the concrete projects—a lot of information and suggestions,
which should help you in implementing as many of your own ideas as possible.
Unfortunately, we have now reached the end of the book, and I wish all readers a lot of
fun in tinkering, constructing, and programming!
{
for (n=0; n<amount; n++) // for all LEDs
{
pos = n + movement; // new position of n, according to movement
while (pos < 0) // as long as underflow (pos smaller than 0)
{
pos += amount; // Add amount (e.g. -1 becomes 19)
}
while (pos >= amount) // as long as overflow
{
pos -= amount; // Subtract amount (e.g. 20 becomes 0)
}
led_new[pos] = led[n]; // save switching state of current LED in new pos.
}
for (n=0; n<amount; n++) // count through all LEDs again
{
led[n] = led_new[n]; // the led array now gets the new values
digitalWrite(pin[n], led[n] == active); // switch LED
}
delay(blink_time); // wait (as defined) until next move
}
If you read the comments, the loop() function is self-explanatory. In contrast to the
previous program, not only one LED is changed on each pass, but all of the values are
shifted. To ensure that no values are overwritten in the led[] array, which we’ll still
need later (in the for loop), the new values are stored temporarily in the led_new[]
array. Afterwards, a second for loop follows, in which the values are written back into
the main array, led[].
96
The number of LEDs and their respective pin numbers must be specified in the program.
For blink_time, a somewhat higher value is recommended here, e.g. 500 ms. The wiring
can be done in exactly the same way as for the 20-LED board. The LEDs are connected
to the positive terminal via a series resistor. For a power supply, a small lithium-ion
battery is used, whose positive wire is connected to a single-pin socket, which you see
hanging in the air. To turn the circuit on, this socket is connected to the pin (next to the
two resistors above).
To save power, the voltage regulator and the power LED on the Pro Mini board have
been removed. The Pro Mini is then like a LilyPad. For details, see section 13.4 on
page 274. For battery operation, I also recommend battery protection:
97
We use the sketch in 13.3b for this. Everything about integrating battery protection into
other projects is explained there. The battery check is only necessary every few
seconds. But, we can also insert it here in loop(), executing it on every pass. It is
important (as described there) to turn off all LEDs before going into standby. If LED 13
is equipped, we should also set Pin 13 as an input before standby. Then the on-board
LED will also be off.
98
The configuration in Fig. 7.2a has another advantage: Complete flexibility in the
switched voltage. While the microcontroller is operated with VCC (e.g. 5 V), you can use
another voltage for the power section (V2 in the
image), e.g. 12 V.
99
100
The BC337 comes in types -16, -25, and -40, which roughly
BC337-40
indicates 1/10 of the amplification factor. What is called A, B,
max. 45 V
and C in the BC547 is called -16, -25, and -40 here, but again,
max. 0.8 A
the amplification factor is significantly lower for large currents. Now,
the BC337-40 has the advantage that you can switch the full 800 mA without needing
too much control current from the Arduino. If you only need under 500 mA, you can
also safely use the BC337-25.
101
The BD435 needs additional cooling for current of about 2 A upward. However, a small
aluminum plate or a mini heat sink is sufficient for this.
There are, as described on page 45, also small infrared thermometers, about the
size and shape of a laser pointer, which allow quick and non-contact temperature
measurement, available from China for just a few euro.
102
These transistors are relatively small, in an SMD package, so they are usually soldered
directly onto the traces of a PCB, as in the image on the right. In contrast, the
transistors in the middle picture were glued with the front side on a small heat sink and
wired freely. The white blobs under the transistors are heat-conductive glue. After
gluing, you should wait at least one day before proceeding with further work.
The pin assignment is the same for all versions. The letters AYWW on the picture
(before the actual designation) stand for place of manufacture, year, and week.
103
104
If inductive loads (motors, relay coils or similar) are used, we should connect the COM
terminal to the loads’ positive poles (in the case of different voltages, to the highest
one). The diodes then act as freewheeling diodes and protect the electronics. For other
loads, such as LEDs, you do not
need to connect the COM pin.
This is also quite practical if we switch several, for example five, different loads. Two
switching stages would then be left unused, but it’s better to connect three stages
together for the load with the highest current, or two stages each for the two largest
loads.
105
The diode may appear to be useless here, because it is operated in reverse direction,
but such a relay consists of a coil, and coils have the special ability to generate short
voltage pulses in the opposite direction when they are switched off. The diode now
short-circuits this pulse, because otherwise the high voltage could destroy the
transistor.
One advantage of a relay is the complete separation of the two circuits. What is
switched does not have to have the same ground potential as the Arduino, but can also
be, for example, a device with 230 V mains voltage. The connection of the two circuits
in the relay is only mechanical, but galvanically-separated.
106
Fig. 7.3.1c: Solid-state 10 A and 25 A relays, as well as a special SSR heat sink
Larger SSRs can switch 10, 25, or even more amps, but they require a heat sink to do
so, which they’re mounted to on their metal undersides.
107
Instead of regulating the current or voltage of an LED, we can switch it on and off very
quickly, a hundred times a second or even faster, so that you don’t see the fast flashing.
If we turn it off and on in equal duration, it appears as if the LED is constantly lit at
half-brightness. If we then change the duration (width) of the individual pulses, we can
dim the LED (in very small steps) from 0% to 100%.
The graph shows different pulse widths from 0% (completely off, so no pulses)
to 100%, where the signal is always on. In the middle, at 50%, the signal is on for
the same duration as it is off. We can now clearly see how, from left to right, the
color in the background changes more and more from white (switched off) to
gray (switched on).
Now, you could program such a PWM control with delay() instructions of different
duration, so that the Arduino always switches on and off and waits in between for
different lengths of time, but then the Arduino would spend most its time waiting.
However, it can be done much easier and more efficiently: On several of its pins, the
ATmega328 (and 168) can automatically output PWM signals. We only have to tell it
once, with a single byte, what the “duty cycle” should be. At 0, the signal is always off,
at 255, always on. 128 is the middle setting.
An automatic PWM signal of 490 Hz can be generated at Pins 3, 9, 10, and 11. This is
already far more than we need for brightness control, because we can only notice flicker
at well below 100 Hz. At Pins 5 and 6, we even have a 980 Hz PWM control signal
available. But, we should only use this if we really need such a high frequency, because
108
system timer millis() and the delay() function use the same internal timer, so there can
be slight interference with the pulse duration here. The 490 Hz PWM signal should
always be free of interference.
Syntax: analogWrite()
analogWrite(3, 64);
In this example, a pulse-width modulated signal with a duty cycle of about 25% is
assigned to Pin 3. It works exactly like digitalWrite(), with the only difference being
that the second value is not LOW or HIGH, but a number from 0 to 255.
You can use analogWrite() at pins 3, 9, 10, and 11 with a PWM frequency of 490 Hz,
and at Pins 5 and 6 with a frequency of 980 Hz. It is not necessary to declare the
corresponding pin as an output before using the pinMode() function.
109
Red, green, and blue are the colors perceived by the human eye. Two of them together
can make magenta, yellow, or cyan, and all three together make white, as in the left
half of the picture. However, red, green, and blue are not suitable for printing at all. If
you were to print with red, for example, you wouldn't be able to mix any other color
with it, because the red would already darken the green and the blue, and any other
color could now only take away the red part, and the result would be black. The
individual printing inks may therefore only darken one color at a time. Cyan darkens
red, yellow darkens blue, and magenta darkens green. These colors can be mixed
during printing, as in the right half of the picture.
Magenta, cyan, and yellow, on the other hand, would be unsuitable as LED colors
because each already consists of two colors of light (magenta, for example, consists of
red and blue). So, only very pale colors could be mixed. Therefore, for light effects we
always need LEDs in red, green, and blue to be able to mix all colors.
110
Before we get to the sketch, a few words about the sine and cosine functions, because
we use the cosine here.
a = sin(b);
The mathematical sine function generates a sine curve with amplitude of ±1. A value
of 2 * Pi (for “b”) corresponds to one oscillation. Similarly, with cos(), you get the
cosine value.
111
byte pin[3] = {9,10,11}; // the PWM pins used for red, green and blue
float lag = 25000; // the higher the slower (default value 25000)
float pos[3] = {0,0,0}; // current sine wave positions of the 3 colors
float factor[3] = {1.618034, 1, 2.618034}; // speeds for red, green & blue
byte brightness; // current brightness value (0 to 255)
byte color = 0; // current color
Here, TWO_PI is first defined as a constant. In the pin[] array, the pins for red, green,
and blue must be specified. But, be careful: We can only use the PWM-capable pins
here. The values in the pos[] array contain the current positions in the sinusoidal
brightness curves of the individual colors. The factor[] array provides three different
velocity factors for the respective three colors, because the brightnesses should not
change synchronously, but rather generate pretty colors. By the way, the two
seemingly arbitrary numbers before and after the “1” are the “golden ratio” (Phi) and
its square, respectively. These ratios cannot be represented as an exact fraction, so the
overall color never repeats exactly, even after a long time. The “brightness” variable is
used to buffer the current brightness values, and “color” is always the number of the
color that is currently being processed.
void setup()
{
// Unusually, there is nothing to do here.
}
void loop()
{
pos[color] += factor[color] / lag; // depending on color factor and speed
if (pos[color] >= 1) // as soon as position is at least 1
{
pos[color]--; // reduce by 1 (a complete sine oscillation)
}
In loop(), the current color's position, pos[color], is increased very slightly at first. If
the value reaches or exceeds 1, it is reduced by 1. Thus the position runs very slowly
from 0 to 1, repeatedly.
112
Then, the brightness of the current color is calculated. 127.5 is half of the maximum
value (255). The cosine function adds another ±127.5, so that the total result is
between 0 and 255. With round(), the value is rounded. The factor TWO_PI is
necessary, because, with the cosine function, a complete wave extends from 0 to 2
times Pi, whereas our position value in pos[] only goes from 0 to 1. You could have
used the sine function (instead of cosine) and added the value (instead of subtracting
it) but, this way, the brightness values start at 0 and the color change starts at black
after switching on (i.e. with the LEDs switched off).
Now, the next color is processed, and, if necessary, reset, so that the next color is
processed on the next loop pass.
Usage
When the program starts, all three LEDs are turned off immediately, and then they
begin changing their respective brightnesses at different speeds. The mix of all three of
them produces pretty colors, which you can recognize as as mixed colors only if you
don’t look directly at the single LEDs, but at the combined light of all LEDs, e.g. if you
illuminate something with them.
113
What happens with our PWM signal here? Fig. 8.2a: RC low-pass
Fig. 8.2b: Low-pass filtering a PWM signal with a 75% duty cycle
The graphic starts on the left, showing a switched off signal (duty cycle 0%). The output
voltage at the capacitor (that’s the red line) is also 0. Then, a PWM signal with a duty
cycle of 75% is sent to the Arduino output, and, as the red line impressively shows, the
capacitor is already charging very slowly from the first pulse, always in a way that
corresponds to the current switching state. With each active pulse, the voltage
increases a little, and in each gap between pulses, it decreases again slightly.
Eventually, the voltage settles at a level that corresponds to the duty cycle. In our
example with a 5-volt pulse voltage and a duty cycle of 75%, this would end up at
about 3.75 V.
We can also see from the red line that the DC voltage is not perfectly smooth. A small
residual ripple remains. We can reduce this with a larger capacitor or resistor, but, let’s
see what happens then:
Fig. 8.2c: Low-pass filtering with a larger capacitor on the PWM signal
Here the capacitance was doubled. The residual ripple has thus halved. On the other
hand, it now takes twice as long for the signal to settle at the 75% level. So, choice of
components for the RC circuit is always a compromise between ripple and signal
sluggishness.
Time constant τ
As a guide, the time constant τ (Greek: “tau”) can assist. τ denotes the time it would
take to reach the final value (75%) if the capacitor voltage maintained its initial rise.
114
In fact, the capacitor voltage has adjusted to the final value after a time unit τ of a
good 63%. This can also be seen in Figure 8.2d by the small vertical red mark, which
corresponds, in this example, to the time unit after 6 clock cycles. The red curve has
already approached the target value here to a good 63%. After two time intervals, it
would be 86.5%, and after 3, 95%. After 5 time intervals one assumes a (nearly)
complete adaptation at over 99%. The voltage at the capacitor has then completely
reached the target value of 75%.
Calculation
τ is very simple to calculate: τ = R C
In our example (4.7 kΩ and 100 µF) this means: τ = 4.7 kΩ 100 µF = 470 ms
After just under half a second, the voltage at the output reaches a good 63% of the
target value. After 2 seconds, it’s already about 99%.
Ripple
The ripple can also be calculated approximately: ΔU = Ub / (4 f R C)
The triangle (Greek: “delta”) stands for the difference, in this case for the ripple. Ub is
the operating voltage (which we simply equate with the pulse voltage here), f is the
PWM frequency, and R and C represent the components used. So, let’s do the math:
A little more than 5 millivolts, which is very little and thus an excellent result. If we
reduce the capacitance by a factor of 10, to 10 µF, or the resistance to 470 ohms, the
ripple increases to 54 mV, which is still quite acceptable. Any change in the PWM value
would then be reflected achieved after just a fraction of a second.
115
Two-stage filter
Another option is to use two RC filters in series, as in
this example. Then you can choose much smaller
capacitances (or resistances). While there is still a
slightly higher ripple after the first electrolytic
capacitor, you get a very clean DC voltage after the
second one. An exact calculation is difficult because of
their mutual influence on each other.
Fig. 8.2e: Two low-pass filters
116
T1’s base voltage results from the board voltage and the duty cycle, minus the voltage
drops at R1, R2, and R3 due to the base current. The base current, in turn, depends on
the transistor’s amplification factor, which we don’t know exactly.
In short: An exact pre-calculation of what current flows from which PWM signal is not
possible here. But, we solve the problem by feeding back the emitter voltage to an
analog input. Current and voltage in R4 are proportional to each other (U = I R). So, if
we know what current is to flow, we also know the voltage that must be present at R4.
We can now measure this exactly and correct the PWM signal repeatedly until we get
exactly the right voltage at R4 – and thus the desired current.
117
The prerequisite for all this is, of course, that the corresponding current can flow at the
collector of the transistor. For this, a load with a voltage source must be connected,
which then also draws current. Without a load, the program would set the duty cycle to
maximum (100%) and still no current would flow (apart from the much lower base
current).
Customization
Depending on the required output current (or current range), we can also adapt the
circuit. With an R4 of 1 Ω / 1 W, we can control a maximum of 1 A, because 1 V drops
and 1 W is used by the resistor. But I recommend not loading the resistor completely to
its limits, and rather using a 2 W resistor for currents above 800 mA. For even higher
currents, we have to reduce the resistor value to 0.47 Ω, because a maximum of 1 volt
is permitted to drop off. We cannot measure more than that (with the internal reference
voltage of 1.1 V ±10%).
The exact resistor value used must be entered into the sketch above as a defined
constant so that the current is correct. You can also use a 0.47 Ω resistor for smaller
currents, if you want to keep the voltage drop at R4 small, but the resolution of
measurement will be reduced somewhat.
If the transistor has to handle more than 1 W, we need a cooling plate or a small heat
sink. The power dissipation results from the transistor’s collector-emitter voltage
(essentially the difference between the operating voltage and the voltage applied to the
load), multiplied by the maximum current flow. Example: At an operating voltage of
15 V, of which 10 volts drop out at the load (i.e. max. 5 V at the transistor), a current of
max. 200 mA may flow without a heat sink, because 200 mA 5 V equals 1 W.
Before we get to the sketch containing the current-control source code, on the next
page is an overview of the new language elements used in it:
118
The & sign is the bitwise AND operation – used to compare numbers bit-by-bit. In the
result, only the corresponding bits that are 1 in both numbers are set to 1. For our
example, this means, bit-by-bit: a = 00010101 & 00000111 = 00000101, which is 5
in decimal.
a = 21 | 7;
Like AND, but OR instead. In the result, every corresponding bit is 1 if it was 1 in at
least one of the compared numbers. For the example: a = 00010101 | 00000111 =
00010111, which is 23 in decimal.
a = 21 ^ 7;
In the XOR (exclusive or) operation, a result bit is 1 if exactly one of the
corresponding bits in the inputs was 1, i.e. if the bits in each number is different. For
our example this means: a = 00010101 ^ 00000111 = 00010010, which is 18 in
decimal.
a = ~21;
The NOT operator is only applied to a single number and is used as a prefix. It simply
causes an inversion of all bits. In our example, the binary would be: a = ~00010101
= 11101010, which is 234, provided it is a single byte. (All of these bitwise operators
can also be applied to int and long variables.)
a = 21 >> 2;
This shifts the individual bits of the number 21 two places to the right. Binary
00010101 (21) becomes 00000101, which is 5. Shifting to the right by one bit is
equivalent to dividing by two. In this example, when shifting by two places, it is
divided by 4, always rounding down. (The decimal part of the division basically
corresponds to the bits that are simply discarded as they fall off the right side when
shifting.)
The empty positions on the left (here the first 2 positions) are always filled with
zeros. Negative numbers are an exception, because with char, int, and long types,
the first bit being a 1 indicates that the number is negative. If there is a 1, 1s are
filled in from the left as the number is shifted right.
a = 21 << 3;
As you can already guess, the bits are shifted three places to the left here. Binary
00010101 becomes 10101000, which corresponds to 168. A left-shift by three places
corresponds to a multiplication of 2 * 2 * 2, i.e. an eight-fold increase. If numbers fall
out on the left, this results in an overflow. The newly-gained digits on the right are
always filled with zeros.
119
#define current_pin A0
#define pwm_pin 5
#define led_pin 13
#define r4 1.00 // Resistor R4
#define reference 1100 // Internal reference (calibrate!)
Again, a few constants are defined first, the pin for current measurement (A0), the
PWM pin (5) and the pin for the on-board LED. The exact resistance value for R4 is
specified for the purpose of current measurement, and it’s 1 ohm by default (for
measurements of up to 1 amp). In order to use the value in floating-point format, at
least one decimal place should still be specified. Finally, the internal reference voltage
of 1100 mV is specified, but this can deviate by up to 100 mV. Therefore, we have to
calibrate the circuit later.
Now, the necessary variables are defined. set_current is the current setting, which is
set here (as an example) to 40 mA. In current_16, 64 measured values are summed
repeatedly, so the single 10-bit values of the AD-converter add up to a 16-bit value.
pwm_14 does the same with the 8-bit PWM values. Later, current_16_factor is used to
calculate the actual current. The 64-time summed values are then summed again for
one second in the “average_” variables, to be output via the serial port.
120
There’s also a variable for the current system time, and for the time of the next output
(display_time), which is incremented by refresh_time. Then there are several pulse
width variables, which will be explained later.
void setup()
{
pinMode(current_pin, INPUT); // Set inputs and outputs
pinMode(led_pin, OUTPUT);
digitalWrite(led_pin, LOW); // LED off
Now inputs and outputs are defined in the setup and the internal LED is switched off.
Serial transmission is activated, the A/D converter is set to internal reference and the
factor for the coming current calculation is defined. This depends, as you can see, on
the reference voltage, the ranges of the variables (16-bit) and R4. (Now, one could
erroneously think that the maximum value of the A/D converter is not 1024 but 1023,
and that the 64-fold value should therefore be somewhat smaller. In fact, the reference
voltage always corresponds with the value 1024.) Finally, the system time is read and
the display time is set.
void loop()
{
// Sum current measurement and pwm:
current_16 += analogRead(current_pin);
pwm_14 += pwm;
In the loop, first the current is measured and added to current_16 – the same with the
PWM value. 64 additions of 8-bit values result in a 14-bit value.
Then, loop-counter “n” is incremented, and the next program block is executed after
64th pass. For this, only if the 6 least-significant bits of “n” (i.e. the bits counting from
0 to 63) are all 0 (which is the case for n = 0, 64, 128, and 192) then the code block
in braces is executed.
121
Here, the actual current is first calculated from the 64 summed values. Then, the
already-summed values as well as the current setting, set_current, are summed up
again in the respective “average_” variables. The number of additions is tallied using
the count_average variable. The values used are then reset to 0 for the new (64-time)
addition.
122
Now the new duty cycle value for the pulse-width modulation is determined. If no
current is to flow, the value will be 0. Otherwise, the deviation (nominal value minus
actual value) is calculated, and a fraction of it is added to fine_pwm. Tests have shown
that a factor of 0.025 is optimal. Depending on whether the current is too low or too
high, this share can be positive or negative. The rounded result is now added to the
previous PWM value and then subtracted from fine_pwm, so that fine_pwm still
contains only a very small value (±0.5). The PWM setting can only be an integer, but
the small remainder from the rounding is not lost, and can be taken into account in the
next passes.
last_pwm is always the PWM value from the previous pass. We check whether this
value is 0, which would mean that the current was previously set to 0. In this case, 34
is added to the PWM value. This is how much is needed to reach the transistor’s
threshold voltage – lower than that, and no current flows at all. Furthermore, pwm is
limited to the allowed range (0 to 255) and, in case of a change (since the last pass) it
is output as a PWM signal to the pin. Of course, last_pwm must then be set to the
current value for the next pass.
Now the LED on the board is rapidly controlled. If PWM is not 0, but also not 255 (that
means if the current is on and also a load is connected), the LED is turned on, else it is
switched off.
123
Now, we’re back to the part of the program that’s executed at every pass of the main
loop (i.e. not only every 64th pass). The system time is read and compared with the
predetermined display time (display_time). If the time is reached, the remaining part
of the program is executed. First, the display time is reset by setting refresh_time i.e.
1000 ms, or one second, further. Then the “average_” summed variables are divided
by the counted values to get the average.
// Serial output:
Serial.print("PWM : ");
Serial.print(average_pwm / 163.2); // PWM 0 to 100% (max. 255 x 32 = 16320)
Serial.println("%");
Serial.print("Measurements: 64 x ");
Serial.print(count_average); // number of measurements
Serial.println("");
Now that the averages are available, the data can be output serially. If you have
connected the serial cable to the PC, you can now open Serial Monitor in the Arduino
IDE (by using the magnifying glass icon in the upper-right corner) and read the output.
If there is no serial connection, it won’t matter – the data then simply go into the void,
but the program works just the same.
Finally, the “average_” variables and the corresponding counter are reset, so the sum
and count for the next second can restart.
124
Functionality
The program adjusts the current for the con-
nected load to the specified value. In Serial
Monitor (image on the right) the data can be
displayed: first the real flowing current, the
current setpoint, the PWM’s duty cycle set-
ting, and the number of measurements from
which the displayed values (over one second)
were formed. This display is updated every
second.
Example: If we want to charge a 12-volt car battery (fully charged it’s over 13 V), we
need a voltage source of at least 15 V. The negative pole of this voltage source is
connected to the negative pole of the circuit, i.e. to GND. The positive pole of the
voltage source then comes to the positive pole of the battery to be charged, and the
negative pole of the battery to the collector of the transistor.
Calibration
In the program sketch, at the constants, the internal reference voltage is specified,
among other things. This is very constant, but not very accurate. That means we have
to calibrate the circuit once at the beginning to get permanently accurate
measurements – and thus the exact current. To do this, we first open Serial Monitor in
the Arduino IDE. Then, we connect a load in which the desired current (e.g. 40 mA) can
also flow. The displayed PWM duty cycle must be below 50%, and the set current of
40 mA should actually flow. However, you can also set a higher current for calibration,
and the measurement will be more accurate.
125
reference_old means the reference value (1100) entered into the sketch. set_current” is
the current specified in the sketch, and actual_voltage is the voltage measured at R4, in
millivolts. As a result, we get the correct reference value, which we now enter (rounded
to an integer), after which we must of course re-upload the sketch to the board. The
value should be between 1000 and 1200. Otherwise, something is wrong.
If a resistor other than 1 Ω is used for R4, we still need to divide the measured voltage
(“actual_voltage”) by R4. Then, the formula is:
Now you can also extend the program individually, adapt it to the desired application,
add your own program sections in the loop, assign other values to set_current,
and so on.
Warning: If the load is disconnected during operation, the control loop tries to
compensate for the missing current and increases the control value. If the load is then
reconnected, too high a current will flow for a short time until the electrolytic capacitors
at the base have discharged accordingly. The operating voltage must therefore also not
be too high, so that the components (load, power supply, and transistor) are not
damaged when the transistor is briefly fully-saturated.
The next circuit, the “Lithium-ion battery testing and charging station” is safer. This
circuit can also be used as a constant-current source. For this purpose, in addition to
the battery sketch in section 8.5, there is also a constant-current sketch in section 8.6.
This is safer because the collector voltage is also measured there, and thus immediately
detected when the load is disconnected. The current is then downregulated and only
rises again when the load is reconnected. For most applications, the next circuit is
therefore more suitable. Only if a load is to be permanently installed, and thus always
remains connected, or if even with direct connection (without the current regulation)
the load would not be overloaded, then there is no danger in using the simpler version.
126
Besides that, the circuit even monitors itself and automatically reduces its charging
current if there is a risk of the transistor or power resistor overheating.
Here, it makes sense to install a USB adapter (the one from chapter 2.1.1 on page 22
with not too long a connection cable), because the data is output via serial and can be
displayed in Serial Monitor in the Arduino IDE. The USB connection can also be used to
power the charger, as long as you don’t need too much charging current. If you don’t
need the data output, but just want to charge a battery, you can use an ordinary USB
power supply.
127
Construction on a PCB
We need:
To connect the board to the PCB, I recommend plugging the two long pin headers into
the PCB side with the shorter pins. The board is then plugged in on top, so that the long
pins protrude from the top. Only when everything is connected together (PCB, pin
headers, and board) should one begin with the soldering. The thick line in the
component placement diagram indicates the transistor’s partly-metallic backside.
It is always costly and difficult to etch a PCB yourself, and commissioning the production
of a single PCB is usually not worth it. Fortunately, there’s an ingeniously simple
alternative that’s hardly ever used today:
128
The resistor leads themselves serve as connecting wires. Only an additional longer
ground wire leads from the board, as an additional safety connection between the two
board’s GND connections. (If necessary this wire can be omitted).
We can copy the construction plan here from the page or also download it for free from
the Elektor site at elektor.com/20243 which is certainly the better solution. In order to
place the thumbtacks precisely to the millimeter, you can print out the construction plan
in its original size, cut it out, stick it to the board and build the circuit on it. It’s very
important that the printout is exactly the original size. (Do not fit print to the page
when printing!) First, press the thumbtacks exactly into the middle of the small “x”
marks. The Pro Mini board then goes into place (with a small dab of hot glue), after
which the rest of the components are wired. It couldn’t be easier.
129
130
After R8 come the other resistors. R1 is only shown as pale in the diagram, because
later electrolytic capacitor C2 is installed above it. R4 is soldered in last. You also have
to bend it down a bit at the kink to reach the thumbtack. Then comes the wire bridge.
Its diameter should ideally be around 0.8 mm. Of course, it can also be an insulated
wire. You still have to be able to solder C3 in. With these electrolytic capacitors (C1 to
C3) one bends the leads horizontally apart in a small bow. The negative side of each
electrolytic capacitor is usually marked with a thick bar. At C2, there’s another bend at
the negative lead, so that it leads to the board’s GND connection, while the electrolytic
capacitor is centered over R1. Finally, after adding small capacitor C4, construction is
complete.
The battery is charged at B- and B+. The supply voltage is connected to +5V and 0V, if
not supplied via the USB-to-serial interface. If a separate power supply is used, both
the positive of the power supply and the positive of the battery should be connected to
the corresponding thumbtack (+5V / B+). So, don’t connect the battery (B+) directly to
the power supply, as this would render the voltage measurement slightly inaccurate.
Customizing
Depending on the desired charging
Current (max) Shunt R4 current, we can adapt the circuit. In
principle, the same applies here as I
up to 800 mA (1 A) 1Ω/1W wrote on page 118 for the constant-
current source in section 8.4, and we
up to 1 A 1Ω/2W
can now even use the same table.
up to 1.2 A (1.4A) 0.47 Ω / 1 W Again, we’d better avoid the maximum
currents in parentheses, because they
up to 1.7 A (2A) 0.47 Ω / 2 W fully load R4. If the charge current and
up to 2 A (2.4A) 0.33 Ω / 2 W the displayed milliampere hours are to
be exactly correct, a tolerance of only
1% is permissible for R4 as well.
131
If the circuit has its own power supply, a connection to the PC is not necessary at all if
you don’t need the serial output data. If you’re adventurous, you can even attach a
display (e.g. an LCD2004 as described in section 11.3 on page 250 and replace the
whole serial output block in the sketch with a corresponding block for outputting to a
display. This way, you get a completely independent test and charging station that
requires no PC.
132
On the other hand, if we use our own power supply, which may sometimes supply more
than 5.5 volts, RAW and VCC must not be connected under any circumstances, and we
must connect the power supply to RAW (i.e. to the intended positive terminal), so that
VCC is generated by the board, using its voltage regulator. Otherwise the ATmega chip
will get too high a voltage.
On the board layout, there are two small pads (just below the Pro Mini board) that
almost touch. These can be easily soldered together in order to bridge RAW and VCC.
The thumbtack version, on the other hand, requires a small bridge in the form of a
small wire (with an upward curve) from VCC to the positive terminal thumbtack.
Cooling
The transistor can handle an internal power dissipation of 750 mW without additional
cooling. Since input and output voltage (i.e. operating voltage and battery voltage) are
close together, we usually stay well below this limit.
If the charging station is not supplied via USB, but with its own power supply, a small
cooling plate or a small heat sink may be useful under certain circumstances. This is
especially true if the power supply voltage is significantly above 5 V and a high charging
current (over 1 A) is used. The power dissipation of 750 mW at the transistor can be
reached at times, but this would not hurt, as the circuit then simply reduces the charge
current.
If you attach a heat sink to T1, you can at least double the allowed transistor power
dissipation, p_t_max, in the sketch. The exact power limit can otherwise also be tested
using temperature measurements. Incidentally, the highest power dissipation in the
transistor occurs when the battery is not yet full, but has just reached power_voltage
(i.e. 3.7 V) during charging.
Transistor saturation
At high charge current and low operating voltage (5 V or less) it may happen that the
transistor goes into saturation. In Serial Monitor, you can see that the PWM control goes
to 100%. The transistor can then no longer supply the desired current even when fully
switched. This is not a disaster, either; charging then only takes a little longer.
In the event of saturation, instead of the gain factor, “Sat” is displayed in Serial Monitor,
including if the transistor only comes close to saturation and the gain factor can no
longer be exactly determined.
133
Now we come to the charging station sketch – a very extensive one, which I’ll explain
briefly. Much of it is already explained in the comments.
#define current_pin A0
#define collector_pin A1
#define supply_pin A2
#define power_pin 5
#define led_pin 13
#define r1 100 // the used resistors
#define r2 22
#define r3 22
#define r4 1.00
#define r5 22000
#define r6 4700
#define r7 1000000
#define r8 220000
#define reference 1100 // internal reference (calibrate!)
#define u_be 670
#define r_be 8.6
Here, in addition to the pins used, all resistors must also be specified exactly so that
the current and all voltages can be calculated correctly. u_be is the transistor’s base-
emitter voltage, and r_be the (internal serial) base-emitter resistance. (These values
only approximate the base-emitter characteristics and were determined by
measurement). Later, these values are needed to calculate T1’s gain factor
approximately.
int power_voltage = 3700; // voltage from which charging starts with full current
int empty_voltage = 2800; // voltage from which the charging current increases
int under_voltage = 4400; // undervoltage (in mV)
int over_voltage = 5700; // overvoltage (in mV)
int empty_current = 20; // current in mA before charging current increases
int end_current = 10; // charge current at which battery considered full
int p_t_max = 750; // maximum power dissipation of the transistor (in mW)
int p_r_max = 750; // maximum power dissipation of R4 (in mW)
These variables specify the test and charging station’s settings. First, the two most
important values, the fully-charged voltage and the charge current. The other voltages
and currents define the exact charging curve. (Explanations follow later, after the
sketch.) The two “p_” values at the end limit the power dissipation.
134
135
void setup()
{
pinMode(current_pin, INPUT); // Set inputs and outputs
pinMode(collector_pin, INPUT);
pinMode(supply_pin, INPUT);
pinMode(led_pin, OUTPUT);
digitalWrite(led_pin, LOW);
136
The setup() components are also largely self-explanatory. The three “_factor” variables
are calculated from the resistances, among others. They are later used to convert the
measured values into the corresponding currents and voltages.
void loop()
{
// Sum actual measurements (and pwm):
current_16 += analogRead(current_pin);
pwm_13 += pwm;
collector_15 += analogRead(collector_pin);
current_16 += analogRead(current_pin);
supply_15 += analogRead(supply_pin);
In the loop, the necessary measurements are first performed and all summed up in
their corresponding variables. Then, counter variable “n” is incremented, and the next
program part is executed after every 32nd pass. The 10-bit values from the analog-to-
digital converter have been summed up to a 15-bit value over 32 passes, hence the
suffix “_15” in the variable names. In current_16, there are 16 bits, because the
values are measured and summed up twice during each pass. In pwm_13, the 8-bit
PWM values were summed to a 13-bit value.
Now the program block that’s executed only at every 32nd pass begins. The current,
as well as the operating voltage and the collector voltage, are calculated and then
again summed up in the corresponding “average_” variables (for one second). The old
(summed 32 times) values are then reset.
137
138
{
set_current += 0.1; // increase current only minimally
led = 196 - (45 * set_current / power_current); // 151 to 196
}
else // end voltage not reached yet
{
set_current += 2; // increase current more
led = 100 * (battery - power_voltage) / (end_voltage - power_voltage);
led += 50; // 50 to 150
}
}
else // power phase (maximum current)
{
led = 100 * (battery - power_voltage) / (end_voltage - power_voltage);
led += 50; // 50 to 150
}
if (set_current > power_current) // if current is set too high now
{
set_current = power_current; // limit to maximum current
}
if (mode != 4) // if mode for end voltage was not reached already
{
mode = 3; // set to power mode
}
}
else // final voltage reached
{
led = 196 - (45 * set_current / power_current); // 151 to 196
set_current -= (battery - end_voltage) / 40; // per mV too much reduce 25 µA
mode = 4; // Set mode for final voltage
if (current < end_current) // end current undershot (finish charging)
{
full_counter++; // increase counter for switch off
if (!full_counter) // in case of overflow (switch-off)
{
// Battery full:
set_current = 0;
led = 200; // stays on
digitalWrite(led_pin, HIGH);
mode = 5; // charging finished
end_time = millis();
}
}
else if (full_counter) // if current flows but full counter is already set
{
full_counter--; // count back
}
}
}
139
}
if (set_current > current_limit) set_current = current_limit; // limit current
if (set_current > current_limit_2) set_current = current_limit_2;
if (set_current < 0) set_current = 0; // current can not be negative
In this lengthy section, the current mode, “mode,” is determined depending on the
battery voltage and the setpoint current set_current is adjusted. In addition, the
variable “led” is assigned a value of between 0 to 200, depending on the battery’s
charge state.
The current PWM value is determined here. If no current is to flow, the value is set to
0. Otherwise, the difference between the current setpoint and the actual current is first
determined. (Depending on whether the current is too low or too high, the result may
be positive or negative). Then, a small portion of this is added to fine_pwm (a float
variable). The actual PWM value is then changed to the rounded integer, while this
integer is then subtracted from fine_pwm, leaving only a small value in the range of
±0.5, which is again taken into account in the next pass.
140
Here, the LED blinking is controlled. To do this, led_time is first determined, a value
that continuously decrements from 200 to 0 for one second between the data outputs
(coming up in the next section). By comparing “led” with led_time, the blinking is
controlled in such a way that the light duration corresponds to the value “led.” In case
of an undervoltage error (led == 253) the LED flashes quickly. With overvoltage (led
== 254), it flashes even faster.
141
Now, we check whether display_time has been reached. This always happens only once
a second, because display_time is then immediately incremented by another second.
Incidentally, the braces contain the entire rest of the sketch’s program block. That
means that everything that comes from here on is executed only once per second. The
“average_” variables are now divided by their respective number of individual values to
get the actual average.
142
If the battery is not yet completely charged, the collector current and the transistor’s
amplification factor are determined as accurately as possible. For example, if the
current is small or if the transistor is (almost) in saturation, no amplification factor can
be determined. Finally, the charged energy or the total charged current is updated. The
unusual unit, microampere minutes (instead of milliampere hours) makes sense only
because the “energy” variable is of type unsigned long in order to have values as
accurate as possible, but to still be able to cope with large batteries of several ampere
hours without overflowing.
Now, the current power dissipation (over the last second) in T1 and R4 is determined,
and, if necessary a limit variable is set to reduce the current. It’s sufficient to do this
only once per second, because the power dissipation can only increase slowly, and a
brief, slight overrun wouldn't do any harm, either.
143
144
If the charging process has been running for at least a quarter of an hour
(900 seconds), and a minimum current of 50 mA is flowing, and the final voltage has
already reached 200 mV, and the internal battery resistance has not yet been
determined, then the resistance measurement begins.
In the simplest case, the internal resistance of a current source may be determined
from its open-circuit voltage and short-circuit current, but, in most cases (including
here) this would not be a good idea at all, because I’ve made stranded wires glow in
fractions of a second with accidental short circuits on lithium-ion batteries. This is not
something you do on purpose. Fortunately, there’s a gentler method: changing the
charge or discharge current, and measuring the voltage change across the battery. For
one minute, we switch off every two seconds, then charge, then switch off… etc. The
current and voltage differences are summed up, and finally the battery’s internal
resistance is calculated.
Then, second-counter “duration” is briefly converted into longer time units, which are
needed in the following section for the output.
145
}
Serial.print(seconds);
Serial.println("s");
Serial.print("Bat: ");
Serial.print(average_current, 1); // current with one decimal place
Serial.print(" mA ");
Serial.print(average_battery / 1000); // battery voltage in volts
Serial.print(" V Ri: ");
if (counter < 60)
{
Serial.println("???"); // Ri not determined (yet)
}
else
{
if (ri >= 1) // 1 ohm and above
{
Serial.print(ri); // output in ohms (by default with 2 decimal places)
Serial.println(" Ω");
}
else // below 1 ohm
{
Serial.print(round(ri * 1000)); // output in milliohms (no decimal places)
Serial.println(" mΩ");
}
}
Serial.print("Set: ");
Serial.print(average_set, 1); // current setpoint
Serial.print(" mA ");
Serial.print(end_voltage / 1000.0); // final charge voltage (in volts as float)
Serial.println(" V (maximum)");
Serial.print("Board: ");
Serial.print(average_supply / 1000); // actual board voltage
Serial.print(" V Status: ");
if (counter < 60 && counter) // during Ri measurement
{
Serial.println("Get Ri"); // show info as status
}
else if (mode == 1) // otherwise depending on current mode
{
if (battery < empty_voltage - 300) Serial.println("Totally empty");
else Serial.println("Battery empty");
}
else if (mode == 2)
{
Serial.println("Nearly empty");
}
else if (mode == 3)
{
146
Serial.println("Half full");
}
else if (mode == 4)
{
if (set_current < middle_current) Serial.println("Nearly full");
else Serial.println("Soon full");
}
else if (mode == 5)
{
Serial.println("Full");
}
else if (mode == 6)
{
Serial.println("U_Board LOW"); // undervoltage
}
else if (mode == 7)
{
Serial.println("U_Board HIGH"); // overvoltage
}
else if (!mode)
{
Serial.println("No battery");
}
Serial.print("PWM: ");
Serial.print(average_pwm / 81.6); // 0 to 100% (max. 255 x 32 = 8160)
Serial.print("% LED: ");
if (led > 200) // display error
{
Serial.println("Fast");
}
else
{
Serial.print(led * 0.5, 1); // light duration of blinking in percent
Serial.println("%");
}
Serial.print("T1: "); // Transistor info:
Serial.print(round(pt1)); // power dissipation in mW
Serial.print(" mW Uce: ");
Serial.print(round(uce)); // voltage between collector and emitter (in mV)
Serial.print(" mV Ic/Ib: "); // amplification factor
if (v_factor == -1)
{
Serial.println("Sat"); // transistor (possibly) in saturation
}
else if (v_factor == -2)
{
Serial.println("???"); // current too small for exact measurement
}
else
147
{
Serial.println(round(v_factor)); // amplification factor
}
Serial.print("R4: "); // Info about R4:
Serial.print(round(pr4)); // power dissipation
Serial.print(" mW ");
Serial.print(round(ue)); // voltage (in mV) at R4
Serial.print(" mV T1+R4: ");
Serial.print(round(pt1 + pr4)); // Total power dissipation in mW (T1 and R4)
Serial.println(" mW");
Serial.println("");
}
Here, the current data is output serially as text, once per second.
This part is executed when the battery is already full. After one minute, the battery
voltage is displayed. After 1, 2, 4, 8, 16, 32, and 64 hours (if you leave the battery in
for that long) it shows how much the battery voltage has decreased since charging.
The counter for the individual outputs is the same “counter” variable that was used
previously for measuring the internal resistance.
148
Finally, the “average_” variables and the corresponding counter variable are reset and
ready for the next second’s values.
The final charge voltage for lithium-ion batteries is usually 4.2 V, i.e. 4200 mV. Ideally,
this voltage should not be exceeded (or only slightly, by max. 50 mV). However, there
are also types which may be charged a little higher. If, on the other hand, you do not
need the full capacity, you can save the battery and charge it only up to 4.1 V or even
up to 4.0 V.
This determines the maximum charging current. However rather than always flowing at
the full rate, but it is done according to exact parameters. A total of 6 variables
determine the exact charging curve.
149
The graph shows the voltage horizontally and the charging current vertically (from the
black zero line):
Now, the actual main charge cycle begins with a constant current of power_current
(400 mA), until final voltage end_voltage (4.2 V) is reached. Then the current is
continuously reduced so that the final voltage is not exceeded, but is maintained. What
seems like just a moment in the graph (denoted by a vertical line at the end of the
charging process), can take a very long time in reality.
Only when the current has reduced to end_current (10 mA), is the current switched
off completely and the charging process is complete. Thereafter, the battery voltage can
drop minimally again. In the graphic, this point (no more current after charging, and
almost at the end voltage) is shown as a red dot.
The value end_current can also be set higher if the charge is to be terminated sooner.
Usually it’s 5% or 10% of the maximum current, for example. That would be 20 or
40 mA. The battery voltage is then marginally lower at the end. But you could just as
easily remove the battery earlier. It would then not be completely charged, but maybe
only at 95%.
However, the current curve during charging (especially the maximum current,
power_current) can also be reduced by other factors, e.g. when the transistor reaches
saturation, or when the operating voltage via USB is not very stable and drops to the
undervoltage limit. Likewise, the current is limited when the power limit for transistor
T1 or resistor R4 is reached.
However, all this is usually not bad. We don't necessarily have to reduce the
power_current setting for this reason, because the current is automatically reduced to
such an extent that everything is back in the green zone. Also, the measurement of the
150
charged milliampere hours is not rendered inaccurate by this. Only the actual current
always counts.
In p_t_max and p_r_max, we can specify the maximum power dissipation of T1 and
R4. The transistor can handle about 1 watt uncooled, as does 1-watt resistor R4.
However, to be on the safe side, we limit the power dissipation to 750 mW each. If a
heat sink is used for the transistor, we can increase p_t_max (depending on the size of
the heat sink) to 2, 3, or 4 watts.
Serial output
This is what the output looks like
in Serial Monitor. The baud rate
must be set to 38400.
The second line shows the currently-flowing charge current, the current battery voltage
and the internal battery resistance, if this has (already) been determined.
In the third line are the setpoints: first the charge current that we’re aiming for (it
should correspond to the actual current, if the transistor is not in saturation), then the
desired final voltage.
Incidentally, the actual current and the setpoint always refer to the emitter current. This
facilitates calibration. However, the actual charge current from the collector is used to
calculate the charge in mAh. This is about 1% smaller.
The fourth line shows the current board supply voltage, as well as the current status,
which usually corresponds to the state of charge or the charge cycle, but it also notifies
us of events such as over- or undervoltage.
151
The fifth line shows the current PWM setting. This is usually between 10 and 50 percent.
(At significantly higher values, the transistor is in saturation.) After this, the
LED-blinking duty cycle is shown. At 0%, the LED is always off, at 50% it’s on as long
as it’s off, and, at 100%, always on.
In the sixth line we find the information about the transistor. First, the current power
dissipation in T1, then the collector-emitter voltage (Uce), then the amplification factor,
if it can be currently determined.
The seventh line contains the power dissipation in resistor R4, then the voltage at the
resistor, and at the end, the total power dissipation of T1 and R4.
After charging, the battery voltage is displayed after one minute, which should then be
very slightly below the final voltage, for example 4.192 volts, as can be seen in the
output window. Then, after 1, 2, 4, 8, 16, 32, and 64 hours (should you leave the
battery connected for that long), you will see how far the battery voltage has decreased
from the original voltage (which was initially measured after one minute).
This voltage drop, which is ideally only a few millivolts after a few hours, says a lot
about the quality and the condition of the battery. However, an initial drop of a few
millivolts in the first hours is still OK if this value slows afterwards, else it means that
the battery probably has a high self-discharge and its best days are over.
The most important quality criterion, however, is the milliampere hour number. With
lithium-ion batteries, the charge that can be consumed from the battery is hardly any
less than the charge that it takes back when charging. So, if a really empty battery (and
by that I mean when the voltage is around 3 volts) is fully charged (usually up to
4.2 volts) then the displayed charge in mAh should also correspond relatively accurately
with its actual capacity.
Internal resistance
If a battery is under load, its voltage always drops a little. By this I
don’t mean the discharge, because if you remove the load, the battery
immediately returns to its original voltage, to a large extent. When
charging, it’s the other way around. The voltage rises immediately a
little, but goes back down again when you remove the charging current.
The reason for this is the internal resistance. We can think of a real Fig. 8.5h:
battery as something like an ideal battery with a small resistor in series. Actual
battery
152
We can measure this resistance from the voltage changes that occur when the current
changes. To do this, the device (after at least 15 minutes of charging, and if it’s no
more than 200 mV below the final voltage) switches the charging current off and on
repeatedly, for periods of 2 seconds each, over the course of one minute.
The internal resistance of larger lithium-ion batteries should ideally be well below
one ohm. If the value is higher, this could also be due to the charger’s contacts. Cheap
battery holders (for example for 18650 cells) often already have a devastating contact
resistance of more than one ohm, which of course also factors into the measurement.
Perhaps the bigger problem is that charging at high resistance takes much longer. The
final voltage seems to be reached faster, but the battery is far from full, because the
voltage drops at the contacts. The current is reduced earlier and to a greater degree,
and charging takes longer. If neither this nor the falsified internal resistance bothers us,
we can use a cheap battery holder. Otherwise, one with gold-plated contacts is
preferable.
Calibration
Accurate calibration is very important here. Therefore, we carry it out in two steps to be
on the safe side. Uncalibrated, the values could deviate by up to 10%. In extreme
cases, a battery could be charged up to 4.62 volts, and it would not forgive that.
First of all, we have specified the internal reference voltage at an estimated 1100 mV in
the program sketch above. With this, we can now simply charge an (empty or
half-empty) battery. What’s important is that the final voltage is not yet reached,
because this could still be set too high when uncalibrated.
Now, we use Serial Monitor, which shows us the values every second. While we keep an
eye on the current, we measure the voltage at R4 with an accurate multimeter, in its
millivolt range. Then we can do the math:
153
Once we have made this adjustment and uploaded it to our board, we now take care of
the most important value, which needs to be the most accurate – the battery voltage.
Here, too, we should use an accurate multimeter. But we don’t measure at the battery
directly, rather at the corresponding connections on the board, i.e. B+ and B-. Then we
can calculate again:
Note: reference_old is now no longer 1100, but the already-changed value. The
actual_voltage is again the voltage measured using multimeter, this time at B+ and B-.
Monitor_voltage is the battery voltage shown in Serial Monitor, on the second line.
Both voltages should now (after the first calibration) differ only slightly, so that, in the
equation, almost the same value is calculated again. The new value is optimized for the
measurement of the battery voltage, because this must be the most accurate.
We can also enter the higher of the two calculated reference values in the sketch, and
thus ensure that neither the charging voltage nor the charging current are higher than
specified. If the two values differ by more than 3 percent, there is either an error or the
resistors have too much tolerance.
Usage
The charging station is now ready for use. In Serial Monitor we can observe the
charging process. Later, if everything works as it should, we can also use the circuit as a
simple charger (without the serial connection). Then, the LED on the board shows us
the most important information: If it’s off, no battery is inserted; if it’s constantly on,
the battery’s full; if it flashes, the battery is being charged. The duration of its flashing
gives us information about the charging status. If it flashes only briefly, for example,
the battery is empty. If it lights up almost all the time and only goes out briefly, the
battery is almost full. In the event of an undervoltage error, it flashes rapidly, and, for
overvoltage, very rapidly, but I do not recommend testing this.
Since there is still plenty of memory left in the ATmega328, the circuit can be expanded
as desired. With a display (e.g. a 2004 LCD display) and button(s), we could, for
example, set the power_current and end_voltage variables in steps within setup(). The
program block performing the serial outputs could be replaced by one sending output to
our display. In this way, we would have a completely independent device, using a
display, as in section 11.3 on page 250.
154
Component specification
max. 31 V 150 k 33 k
Of course, this circuit can still not completely replace a high-quality laboratory power
supply. For constant loads (such as incandescent lamps, heating elements, power LEDs,
etc.) the regulation works very well, as well for charging rechargeable batteries and for
controlling motors. But, strictly speaking, only the current is directly controlled by this
current source.
155
All other adjustments, such as voltage limitation, are made (as quickly as possible) via
the current setting, so care should be taken with loads containing internal electronics
that can change their current consumption quickly. A sudden drop in current
consumption can cause a brief overvoltage.
The output voltage becomes a bit more stable if we add an additional 1000 µF capacitor
and 1 kΩ resistor in parallel to the output – i.e. in parallel to the load. This resistor
should be able to handle of a load of 1 watt. (Up to 20 V, a ½ W is sufficient, and for up
to 15 V, ¼ W suffices.) However, these parts are not yet included in the basic circuit
diagram:
The circuit diagram is almost identical to that of the charging station. Instead of the
battery we now have another load (which could also be a battery, e.g. a 12 -volt
lead-acid battery). Instead of the 5-volt operating voltage, we now have a variable
operating voltage, which can also be higher – up to a maximum of 31 volts, and instead
of the dashed connection between the total operating voltage and VCC, we now have a
dashed connection to RAW. This means that there must be no connection from RAW to
VCC, and the connection to RAW must be disconnected if the operating voltage is higher
than 12 volts. In that case, the Pro Mini board needs a separate supply (5 to 12 volts)
to RAW.
Ideally, in the PCB version, you mount the Pro Mini on the RAW pin not with a
continuous pin strip, but with 2 shorter ones, whereby RAW is simply left out if it is not
to be connected. Or just snip off the RAW pin on the header beforehand, on the side
with the shorter pins, so that it is soldered to the board but still missing from the board.
If necessary, we can also cut the trace on the board between RAW and the connector
pin directly in front of it – but, as I said, only if we use more than 12 V.
156
#define current_pin A0
#define collector_pin A1
#define supply_pin A2
#define power_pin 5
#define led_pin 13
#define r4 1.00 // 1W up to 800mA or 2W up to 1A
#define r5 56000 // up to 13 Volt
#define r6 4700
#define r7 1000000
#define r8 82000 // up to 13 volts
#define reference 1100 // internal reference voltage, calibrate!
Here again the pins used, the resistors and the reference voltage are first defined. R5
and R8 must be adapted to the maximum used operating voltage, according to the
table. R4 can be adapted to other current ranges. The reference voltage must also be
calibrated here.
Then come the variables that control the output current. For the connected load, we
can limit the voltage, the current, the power, the amount of charge delivered, and/or
simply the time. p_t_max indicates how much power dissipation the transistor can
handle, and p_r_max is the maximum power dissipation we expect resistor R4 to
handle.
In addition, it is easy to add your own program sections later in the loop, in order to
modify the variable values and thus to control the output.
157
[..]
void setup()
{
[..]
}
void loop()
{
[..]
}
The rest of the program is self-explanatory, thanks to the comments, and is also partly
identical to sketch 8.5, or at least similar. Therefore it has not been printed in its
entirety here. Instead, I would like to explain only the main change, briefly:
To limit the individual values, there are now a few more “set_” variables in addition to
the target current, set_current, for the individual limits according to which the current
is controlled. The smallest value (i.e. the strongest limitation) is set as set_new and
then adopted as the new set_current.
Power supply
The operating voltage should be about 1 to 2 volts higher than the maximum required
output voltage, but, not higher than necessary if possible, because a quick adaptation
to changing loads is not possible with this simple circuit, so a brief overvoltage could
occur in the case of a sudden discharge. If the operating voltage exceeds 12 volts, the
connection to the board’s RAW connector must be disconnected and the board must be
supplied with a separate voltage (5 to 12 volts, min. 100 mA) at the RAW connector.
Of course, the power supply must also be able to deliver the required current.
Serial output
Here, the first line shows the
present values for voltage, current
and power consumption in the load.
The corresponding preset limita-
tions are directly below. The third
line shows the data for the current
operating voltage, PWM duty cycle,
and the current operating mode.
The fourth line shows the transistor’s power dissipation, the voltage between collector
and emitter at the transistor, and the power dissipation in resistor R4. Then, the sec-
ond-last line shows the charge delivered so far. If there is a limit, it is shown in paren-
theses.
The last line shows the elapsed time, and, if this has been limited in the settings, the
remaining time is shown next to it. If this time has elapsed or the charge amount has
been reached, the device switches off automatically. A restart is possible at any time
using the reset button. Time and charge quantity are then reset.
Should all of this not be the case, status UD (undefined) is displayed. This may be the
case for a short time, for example, if a load has just been connected. The current then
increases, but is not limited by anything in the first moment.
Cooling
Without additional cooling, the transistor’s internal power dissipation should be limited
to 750 mW. For the charging station (section 8.5) this was no problem, because the
difference between input and output voltage there was relatively small. This could be
different here. If the output voltage is significantly lower than the operating voltage,
and a high current flows, then a larger heat sink is necessary.
159
If a heat sink is used, we have to increase the p_t_max value in the sketch accordingly.
To do this, we first enter a higher value (e.g. 8000 for 8 watts). Then we do a load test
where Serial Monitor shows more than 1 watt power dissipation in the transistor. This
way we can test the heating. If the transistor does not get too hot, we run the load test
for half an hour or more, and measure the temperature at the transistor. An infrared
thermometer such as the one on page 45 is ideal for this.
Unfortunately, with a finger on the transistor, the temperature can only be estimated
very roughly. It may well become hot. If you put a small drop of water on the labeled
surface of the transistor, it should not evaporate immediately. An increase in
temperature of 75 degrees (e.g. running at 100 degrees from a room temperature of 25
degrees) is the maximum permissible limit. For example, if we have an increase of only
25 degrees at 2 watts, we can allow a maximum of 3 times that, or 6 watts, and enter
the value 6000 in the sketch. If the circuit is later placed in an enclosure, this again has
a detrimental effect on cooling.
For values that we do not want to limit, we simply enter 0. Current and voltage limits
are then automatically set (in setup()) to the maximum permissible values, and there is
no limit at all for power and charge amount. However, at least one of the first three
values must also be sensibly limited, because otherwise current and voltage would
theoretically have to run toward infinity.
The power dissipation in transistor T1, p_t_max, and in resistor R4, p_r_max, (both not
to be confused with the load power, which is limited using max_power) must be
adjusted as necessary. For the transistor you can increase the value significantly if you
give it additional cooling with an aluminum plate. For the resistor, you can enter 75% of
the allowed power dissipation, i.e. 750 for a 1-watt resistor or 1500 for a 2-watt. This
way, we’re on the safe side and the resistor can’t get too hot.
The “on” variable must be set to “true” in order to enable the power source. If you
extend the sketch with your own program sections, you can use this variable to switch
the current on and off. If a charge limit is specified, this variable is automatically set to
“false” when the charge amount is reached.
160
Calibration
As with all applications where precise voltages or currents are required, a calibration
must be performed, because in each ATmega chip the internal reference voltage can
deviate by up to ±10%.
Calibration is basically the same as for the (almost identical) charging station from the
previous chapter (section 8.5). Here, too, there are two possibilities for calibration, by
voltage and by current, and I recommend carrying out both. With the second
calibration, the calculated reference voltage should only deviate minimally. This way we
know that we’ve done everything right.
First, in the program sketch, the reference voltage is specified as 1100 mV. With this,
we start the current source with any load and measure the voltage at R4 with an
accurate multimeter. At the same time, we observe the current we get in the Arduino
IDE’s Serial Monitor. Then we can calculate:
When we have adapted the reference setting and loaded it onto the board, we measure
and calibrate the output voltage for control. For this we first need a stable voltage at
the output. This can be done, for example, by charging a battery with a low current
setting or by connecting another load together with a large electrolytic capacitor (at
least 1000 µF). Ideally, the output voltage should be at least half of the operating
voltage. Then we can measure and calculate accurately:
161
Both voltages (measured and displayed) should now (after the first calibration) only
differ slightly, so that in the equation almost the same value comes out again. But now
we do not enter the new value in the sketch as reference, but the average value from
both measurements. This way we have the best possible accuracy for all values that are
measured and controlled.
We can also enter the higher of the two calculated reference values in the sketch, and
thus ensure that neither the output voltage nor the current are higher than displayed. If
the two values differ by more than three percent, there is an error or the resistors have
too much tolerance.
Usage
After calibration, the circuit is ready for use. We can, for example, operate power LEDs
(with a 9 to 12 V operating voltage) with a precisely specified power max_power or with
constant current max_current. The same applies to mini soldering irons (with a 12 V
operating voltage) or other heating elements.
NiCd, NiMH, and lead-acid batteries can also be charged using this circuit. The voltage
limit is the maximum voltage that can or should be reached during charging. For a 12 V
lead-acid battery, this would be about 14 to 14.5 V, and, for a 6 V battery, about half
that. For NiCd and NiMH batteries, it is 1.45 V, or a multiple of this if we charge several
cells in series.
For the current limit, we enter the recommended charge current for charging batteries.
We omit out the power limit (i.e. enter 0), and for charge quantity max_charge, we
enter (for an empty battery) the battery capacity, or, depending on the recommendation
perhaps somewhat more. You can also enter a fixed time for max_time instead of the
charge quantity. However, we have to convert this into seconds – i.e. multiply the hours
by 3600, or the minutes by 60.
DC motors can also be controlled in this way. The set voltage determines the speed. The
force (i.e. the torque) can be controlled via current limitation, and the power using
power limitation. For example, a motor with strong current limiting has hardly any
power, even if it runs at no load much faster than without current limiting at low
voltage. Much more about the control of motors can be found in the next big chapter:
162
With motors, we first have to distinguish between a few basic types. If you simply want
to convert electrical power into motion, you usually have to deal with normal DC
motors. However, there are also stepper motors, for example, whose positions can be
controlled very precisely. These are used in CNC devices, 3D printers, etc. Brushless
motors, on the other hand, are very effective and wear-resistant – for high performance
in the smallest space.
9.1 DC motors
DC motors are, as the name suggests,
operated on direct current or direct voltage.
If you reverse their polarity, they change
their direction of rotation. To control such a
motor, an ordinary transistor is sufficient for
the simplest case. But, first we should be
clear what exactly we want to do, e.g. do we
need to run it in both directions? And, is it
Fig. 9.1: Two DC
enough to just switch the motor on and off,
motors
or is speed control also necessary?
163
Fig. 9.1.1: Switching and controlling motors with MOSFETs or bipolar NPN transistors
I recommend the version on the far left because it’s the simplest. With a MOSFET you
can also switch loads of several amps very easily. The dotted resistor switches off the
MOSFET, if the Arduino pin is not set as and output. During reset, the pin might be set
as an input, for example. In contrast to the middle circuit, this ground resistor is
connected directly to the Arduino, so that the MOSFET’s switching voltage is not
reduced.
The middle circuit shows a simple example with the well known BD435 from section
7.2.3. If the motor needs more than 1 A, we should increase the base current with a
second transistor (BC547 or BC337) as in the example on the right. The BD435 then
also needs a small heat sink. But the current should not go above 4 A. The BD435
might not forgive us for that.
All three circuits have a diode in parallel to the load, i.e. to the motor, a so-called
freewheeling diode, which we’ve also seen before, namely with the relay in section 7.3.
Like a relay, a motor is also an inductive load. The diode protects the transistor from
voltage spikes (when switching off) and thus also enables clean PWM control.
We use one of the board’s PWM-capable pins (preferably 3, 9, 10, or 11) for this. Using
the analogWrite() function, we can then control the speed as we like – from 0 for off to
255 for full-speed. The transistor circuits described here also all suitable for PWM.
164
If switches 1 and 4 are closed, the motor runs in one direction. On the other hand, if we
close 2 and 3, we get the reverse running direction. If switches 1 and 3 or 2 and 4 are
closed, both leads of the motor are connected to positive or both to negative and the
motor is off. Under no circumstances should switches 1 and 2 be closed at the same
time, or 3 and 4 at the same time, as this would cause a short circuit.
Of course, you don’t build an H-bridge using mechanical switches like this, although you
could do it with relays. (Switches 1 and 2 could be realized using a single relay with a
toggle switch, as could 3 and 4.) Instead, however, you take special ICs that use
transistors internally for switching. Such ICs are usually not controlled with four single
signals for the four transistors, but only with one signal for the left side and another for
the right side. This makes things easier and ensures that both transistors on one side
never conduct at the same time, as this would cause a short circuit. Depending on
whether the input signal is HIGH or LOW, only the upper or the lower transistor
conducts (never both at the same time) and the corresponding motor line is connected
to plus or minus.
The L9110S
A very cheap H-bridge for max. 12 V and 750 mA is the
L9110S. Directly from China, there are small boards for
less than one euro that contain two of these H-bridges, and
which can control two motors in both directions. Fig 9.1.3b: 2 x L9110S
The L298N
If you need more current or a higher voltage,
there are also stronger ICs. For 1 to 2 euro
you can get bigger boards from China that use
the L298N. This IC already has two H-bridges
to control two motors using only one IC. Here,
you can also tie the two H-bridges together if
necessary. The L298N boards even have a
small heat sink and can switch up to 2 A
(connected together, max. 4 A) at up to 35 V
motor voltage.
Behind each of ENA and ENB, there’s a 5-volt pin, so ENA and ENB can also be activated
(as shown on the picture) with a jumper plugged in. Thus, we can use either only the 4
input pins IN1 to IN4 with a 4-pin header, and leave the two enable lines always
activated by using jumpers, or we remove the jumpers and use a 6-pin header to
control all of the pins.
The third jumper, labeled “Supply” here, connects the 12-volt input to a voltage
regulator, so that the board generates its own 5-volt supply. At the same time, the
12 volts is also the power supply for the outputs (i.e. for the connected motors), and
this voltage is allowed to be much higher – up to 35 volts. Only, the voltage regulator
does not like more than 12 volts, which means: If our supply voltage for the motors is
higher than 12 volts, we have to take out the Supply jumper, and supply the board with
5 volts via the Arduino’s VCC pin. We can then apply a higher voltage, up to 35 volts, to
the +12V input. If you want to supply the motor with a lower voltage (7.5 to 12 volts),
you can leave the Supply jumper plugged in. But then the +5V connector is only
166
connected to the Arduino’s VCC, in which case the Arduino has to have power supplied
to it, i.e. if the Arduino does not have its own power supply.
Usually we use Inputs and Outputs 1 and 2 for one motor, and then we can control
another motor at 3 and 4. But, we can also connect Inputs 1 and 2 to each other, as
well as Outputs 1 and 2. We do the same with 3 and 4. This way we can control one
motor, but at double the maximum current. One line of the motor is then connected to
Outputs 1 and 2, the other to 3 and 4. For this, however, the corresponding inputs must
also be connected to each other.
Control
Construction and control with such an H-Bridge board is
very simple, in principle. We need two outputs from the
Arduino for the two inputs of the H-Bridge. The motor is
connected to the two output pins. (If there is a second
H-Bridge free on the board, we can add it by simply
connecting the corresponding input and output lines to
the first bridge). Then, the H-bridge still needs the
operating voltage for the motor, and possibly the 5-volt
VCC supply (dashed).
Fig. 9.1.3e: Control
In the Arduino sketch, we put LOW levels on both
outputs when we want the motor to be off. Depending on which line, we then apply a
HIGH signal to (a or b), the motor will run in one direction or the other.
But we can also apply a HIGH signal to both outputs to switch off the motor. Then we
have to switch one or the other line to LOW to make the motor run in one direction or
the other.
Here again we have a small sketch, with which we can control a DC motor by means of
a potentiometer or an analog joystick, in both directions. The sketch here uses the
min() function for the first time, which I will explain briefly in advance.
167
a = min(a, 255);
The min() function returns the smaller of two specified values. In this example,
variable “a” is limited to 255. If “a” is already smaller than 255, the function returns
the value of a. If a is larger, 255 is returned, as the smaller value.
a = max(a, 0);
The max() function always returns the larger of two values. In this example, “a” has a
lower limit: negative values of “a” return 0.
#define motor_pin_a 10 // signal for one of the motor lines (must be PWM-capable)
#define motor_pin_b 11 // signal for the other motor line (must be PWM-capable)
#define pot_pin A0 // signal from potentiometer
First, the pins to be used are defined – two PWM-capable outputs for the motor signals
and (if you want to use it that way) an analog pin for the potentiometer. Then come
two variables. With “power,” we specify the actual motor speed, which goes from -255
to +255. “threshold” specifies the minimum value, above which the motor should be
controlled. Otherwise it would not be possible to switch off the motor completely using
the potentiometer. You would almost always have a small deviation from 0 (e.g. -2).
The motor would stand still, but would consume a small amount of power.
void setup()
{
// Unusually, there's nothing to do here
}
void loop()
{
power = (analogRead(pot_pin) >> 1) - 256; // power according to the pot position
if (power < threshold && power > -threshold) // if value is near 0
{
power = 0; // switch off completely
}
168
There's nothing to do here in setup(). In loop(), the “power” value (i.e. the current
speed) is defined first. For this, the analog value is measured at the potentiometer pin
and halved (by bit-shifting right by one digit). The range from 0 to 1023 becomes a
range from 0 to 511. 256 is then subtracted from this. Then we have a range from -
256 to +255. Values that are close to 0 (actually, less than “threshold” away from 0)
are then set to 0, so that the motor is switched off completely.
Instead of getting the “power” value from the potentiometer setting using
analogRead(), you can of course replace the first line of the loop with your own
program sections that generate the value and thus control the motor. If the value is
ever above 255 or below -255, the control will later limit it to this range.
Now the outputs are clocked by PWM based on the “power” value. If “power” is
positive or 0, Line A gets the PWM value and Line B is switched to LOW. If “power” is
negative, Line B gets the PWM signal (as positive value “-power,”) and Line A is
switched to LOW. If “power” is 0, both lines get 0 as PWM value and thus a LOW level.
The min() function limits the PWM value to a maximum of 255 in each case.
169
Stepper motors have the special ability to specify every position exactly and to find it
again just as precisely at any time (even after thousands of revolutions). The reason
lies in its completely different operation mode. Whereas a DC motor simply starts
running as soon as you apply voltage, the stepper motor only ever takes a small step to
the next precisely-defined position. In order to jump to the next position (or back to the
previous one), the motor needs a signal corresponding to the respective position each
time. So, you can let the motor run many revolutions with thousands of steps, and then
give it just as many impulses in the other direction, and it ends up exactly at the
starting point again.
Fig. 9.2.1a:
Stepper motor
170
The following image shows how such a stepper motor is controlled. Eight single steps
result in one revolution of the rotor.
In the first step, a voltage is applied to the horizontal coil. The current generates a
magnetic field which attracts the opposite pole of the rotor. If, in the second step, a
voltage is also applied to the vertical coil, the pole of the rotor is attracted by both coils
and it turns an eighth of a turn. In the third step, the voltage is removed from the
horizontal coil. Now only the vertical coil is active and the rotor turns again by an eighth
of a turn. In order for it to rotate further, a voltage with reversed polarity must now be
applied to the horizontal coil in step 4, and so on.
After every 8 steps, the motor has made exactly one revolution. The ninth step then
corresponds with the first again. To reverse the running direction, the step sequence
only has to run in reverse. For example, if the motor is currently in position 7, then
position 6 follows, not 8.
171
Here we now have 4 coils to control the 8 positions. Each coil must be controlled only in
one direction (i.e. unipolar). This way, we can, for example, connect the positive poles
of all 4 coils and connect them to the supply voltage. We can then use four transistors
to connect the individual negative poles to ground. This is a bit easier than using two
H-bridges.
Here, only the vertical or the horizontal coil is activated. The intermediate steps, where
both coils are activated at the same time, are simply skipped. Thus, one has only 4
steps (instead of 8) per revolution.
172
Conversely, the number of steps can also be increased. With special procedures, the
vertical and horizontal coil is then controlled differently, so that additional intermediate
steps result. You get e.g. 16, 32, or even more steps per revolution. More about this
later, in sections 9.2.3 and 9.2.4.
Fig. 9.2.1e Bipolar version with 4 connections, and unipolar version with 5
The unipolar version on the right has the center taps already connected. This line is
usually used as a common positive pole, while the other 4 lines can each be connected
to ground using a transistor. Usually, these 4 lines are then also labeled in the order in
which they are controlled, i.e. first A, then A+B, B, B+C, C, C+D, D, D+A, and then
again only A, and so on.
If the center taps are not interconnected, the motor has 6 leads. It can then be used
either in a bipolar fashion, by not using the center taps at all, or in a unipolar one, by
connecting them and using them as a common positive pole.
With high-quality stepper motors that only take very small steps – and most of them do
– there is another difference from the schematic. The rotor has not just 2 magnetic
poles around it, but many more. Likewise, the coils with their two poles also act
alternately on several places on the rotor. So the motor does much smaller steps and
completes a revolution after not only 8, but, for example after 8 8, or 64 steps.
173
174
example, by running the motor many times exactly one round in the same direction
(each time with a short pause). If we do the math using the correct numbers, the motor
will still stop at exactly the same place even after hundreds of laps.
Sometimes, the gearbox’s relatively large play can be annoying. You can always move
the axis (at the same step position) a little bit back and forth – there’s always a wiggle
in the extent of a few steps. The high gear ratio has other advantages and
disadvantages. The relatively weak motor still has a lot of power. Every angle of rotation
can be controlled very precisely (if you disregard the play), but fast revolutions are,
unfortunately, not possible. For one revolution, the motor usually needs several
seconds.
175
Here, we should also install the 4 freewheeling diodes, which are already in the
ULN2003. Instead of bipolar transistors, you could also use 4 MOSFETs of type
NTD4906N or IRLR8726PbF, as described in section 7.2.4 on page 102. The motor lines
are controlled in the order in which they are connected to the connector. So, first (next
to the red common positive line) orange, then yellow, violet (or pink), and then blue,
or in reverse order if the motor is to run in backwards.
V2 is the voltage that supplies the motor. Depending on the version, this can be 5 V or
12 V. Hence the dotted line to VCC. But, we should not use VCC from the Arduino to
power the motor if the voltage comes from the Pro Mini’s voltage regulator, because it
might be overloaded. We can instead use another 5-volt supply (V2) to drive the motor
and the Arduino.
176
#define pin_a 2
#define pin_b 3
#define pin_c 4
#define pin_d 5
First, the 4 motor pins are defined. I recommend using 4 pins that are next to each
other, so that you can plug a cable with 4-pin female connector on both sides directly
from the Pro Mini board to the ULN2003 driver board.
The motor speed is determined by the “duration” variables. In addition to the actual
“step_” value, there is also the “start_” value, with which we create a short (quick)
acceleration phase when starting the motor. This allows us to achieve much higher
speeds, which the motor would not be able to achieve if it were started cold. The other
variables are self-explanatory.
177
void setup()
{
pinMode(pin_a, OUTPUT); // set motor pins to output
digitalWrite(pin_a, LOW); // and turn them off
pinMode(pin_b, OUTPUT);
digitalWrite(pin_b, LOW);
pinMode(pin_c, OUTPUT);
digitalWrite(pin_c, LOW);
pinMode(pin_d, OUTPUT);
digitalWrite(pin_d, LOW);
real_duration = start_duration;
last_time = millis();
}
setup() is as normal. The motor pins are set as outputs and turned off. Then, the initial
step duration for acceleration and the time are set.
void loop()
{
elapsed_time = micros() - last_time;
if (elapsed_time >= real_duration) // if step duration is reached
{
last_time += real_duration;
In loop(), the elapsed time (since the last step) is first determined and we check
whether the step duration has been reached. Only then is last_time updated and
further motor control executed. It is notable that all the time variables are in unsigned
int format, although micros() returns a long value. Nevertheless, we always get the
correct value, even if last_time overflows every now and then and even if the system
time overflows. After all, elapsed_time cannot be less than zero. Likewise, an unsigned
variable cannot be negative. So, the value is always correct – even if the variable
overflows. And, we’re only dealing with short times of a few thousand microseconds,
so the values are never too large.
178
real_duration = start_duration;
}
}
}
If the current position matches the target position, the target has been reached. If the
motor is still on at that point, we count down from 5 (value of motor_delay). After the
5 passes during which the motor is stationary, all 4 coils are switched off. This small
switch-off delay ensures that the motor actually stops in the desired position. Now the
step duration is set to the initial value.
If, on the other hand, the target has not yet been reached, we check whether the real
step duration is longer than the regular (fastest) step duration. Then, the motor is still
in the acceleration phase and the real step duration is slightly shortened. This odd
formula results from the fact that the real duration also defines the timing of this
correction. If the calculation clock were constant, we could divide the time by a fixed
factor (e.g. 1.02) to accelerate evenly. Here, strictly speaking, we should even have to
work with power calculations, but this formula gives a very good approximation. Here,
the number 50,000 determines how fast the acceleration is. A larger value (e.g.
500,000) would slow down the acceleration.
179
Now (since the target has not yet been reached) a distinction is made as to whether
the motor is already on. If this is not the case – i.e. if it has only just been started,
motor_on is set to motor_delay. This value serves later as a switch-off delay. But, no
movement is executed yet, in order to re-engage in exactly the old position. Only at
the next step (if the motor was already switched on) the position variables are counted
up or down. The “cycle_” variable is truncated to the last three bits at the end. It only
indicates the motor cycle and only counts in the range 0 to 7, while motor_position
may also be a very large (or even negative) number and indicates the total position.
if (cycle_position == 0)
{
digitalWrite(pin_a, HIGH);
digitalWrite(pin_d, LOW);
digitalWrite(pin_b, LOW);
}
else if (cycle_position == 1)
{
digitalWrite(pin_a, HIGH);
digitalWrite(pin_b, HIGH);
}
else if (cycle_position == 2)
{
digitalWrite(pin_b, HIGH);
digitalWrite(pin_a, LOW);
digitalWrite(pin_c, LOW);
}
180
else if (cycle_position == 3)
{
digitalWrite(pin_b, HIGH);
digitalWrite(pin_c, HIGH);
}
else if (cycle_position == 4)
{
digitalWrite(pin_c, HIGH);
digitalWrite(pin_b, LOW);
digitalWrite(pin_d, LOW);
}
else if (cycle_position == 5)
{
digitalWrite(pin_c, HIGH);
digitalWrite(pin_d, HIGH);
}
else if (cycle_position == 6)
{
digitalWrite(pin_d, HIGH);
digitalWrite(pin_c, LOW);
digitalWrite(pin_a, LOW);
}
else if (cycle_position == 7)
{
digitalWrite(pin_d, HIGH);
digitalWrite(pin_a, HIGH);
}
}
}
Now the corresponding coils are switched on according to the current cycle_position.
Two coils should now be on and none should be off, because no matter in which
direction the motor runs, we’ve only had one of the two coils activated. If only one coil
is now activated, the two neighboring coils must be inactive, because, depending on
the running direction one of them was previously active.
181
// Put your own program sections here. With target_position you set new targets.
// Example: In this way the motor runs alternately back and forth with 5 s pause.
if (!motor_on) // only if the target is reached and the motor is already off
{
delay(5000); // wait 1 seconds
if (target_position > 0)
{
target_position -= 4104; // approx. one turn backwards
}
else
{
target_position += 4104; // approx. one turn forward
}
}
}
This section is no longer part of the actual motor control. Here, we place our main
program, which transfers the target values to the controller. In the given example, the
motor simply runs one round back and forth. But, be careful: We may only use delay()
if motor_on is 0 or false, but never while the motor is running, because that would
stop it. Conversely, the actual motor control does not use a delay, so the program
continues to run while the motor is running and other parts of the program can still be
executed.
Usage
We can use this program for any 28BYJ-48 stepper motor control purposes. In the lower
part, we write our own program, which controls the motor. We can specify new target
positions at any time using the target_position variable. To make sure that the motor is
not running anymore, i.e. the previous target has been reached, we should use the
condition “if (!motor_on)”. If the running direction is the same, we can also specify a
new target during operation. If, on the other hand, we want to change the running
direction while the motor is moving, we should first execute “target_position =
motor_position;”, i.e. set the current position as the target so that the motor stops, and
only specify the new target when motor_on is 0. This way, the motor does not change
direction abruptly, but stops, and accelerates again.
182
We can also specify a new value for motor_position at any time in the program
sequence in order to calibrate the position. With “motor_position = 0;” we define e.g.
the current position as zero point. With variable step_duration, we can change the
speed. The shorter the steps are, the faster the motor runs. Where the limit is, you just
have to test, because it depends on several factors from operating voltage to
mechanical load. To be on the safe side, I recommend using only two-thirds of the
maximum possible speed, or 1.5 the step time.
If you have to control two or three stepper motors, you can duplicate the main program
segments, with similar names for all variables, e.g. with x_motor_position,
y_motor_position, z_motor_position, and so on. But it’s also necessary to duplicate all
time variables, as well as the “if” comparison for whether the corresponding step
duration is reached. In principle, we have to loop the entire motor control two or three
times (for the respective motor) in succession.
The A4988 usually comes a small board with everything you need to control it. The pins
are commonly labeled on the underside.
183
Pinout
On the left here are the input pins that we can control with
output signals from the Arduino.
RESET: This pin must also be HIGH during operation. A LOW signal not only turns off
the motor, but also resets the internal position. We can connect this pin to the Arduino’s
reset pin.
MS1, MS2, MS3: The signals at these three MS1 MS2 MS3 Resolution
inputs determine the motor’s step resolution.
In addition to full- and half- step operation, L L L full-step
you can also select quarter-, eighth- and H L L half-step
sixteenth- steps. If there’s no reason not to, I
L H L quarter-step
recommend using the finest resolution, i.e. to
set all three lines to HIGH. Usually, we always H H L eighth-step
work with the same resolution. Then, we don't H H H 1/16-step
have to connect these three lines to Arduino
outputs, but can connect all three to HIGH, i.e. VCC.
ENABLE: This line must be LOW to control the motor. It is best to use this line to switch
the motor off between runs using a HIGH signal, while RESET and SLEEP always remain
high.
GND: Top-right in Figure 9.2.3b. This is the ground pin for voltage VDD.
VDD: This is the voltage corresponding to the logic levels at the input. It is connected
to VCC from the Arduino. VDD may be in the range of 3.0 to 5.5 volts, so you may also
use e.g. a 3.3 V Pro Mini board a LilyPad.
1A & 1B: These are the output pins for the first coil of the motor.
2A & 2B: These are the output pins for the motor’s second coil.
GND: This is for the ground for the motor voltage. It is best to connect the respective
ground lead of the two voltages (VDD and VMOT) to the associated ground pin next to
the voltage pin, but still connect the two ground pins to each other as well.
184
VMOT: The voltage for the motor (8 to 35 V) is applied to the last pin (bottom-right in
Fig. 9.3.2b). 12 or 24 V is preferable.
We then have to set a reference voltage at the potentiometer, which is applied to the
sliding contact of the potentiometer itself. The well-known formula R = U / I or
U = I R is applied, but, due to the internal electronics, multiplied by a factor 8. The
voltage to be set is thus:
U=8IR
R is the value of one of the two identical resistors. I is the current we want to adjust.
The motor does not have to be connected yet, because no motor current flows during
the adjustment. We only set a voltage, which will be used to limit the current later.
The A4988 can handle a maximum of 2 A, so we must not set the current higher. Rather
stick to just 1 A, or 1.5 A, which should be good enough for most applications. For 1 A
with shunt resistors of 0.1 ohms each, this means: U = 8 1 0.1 = 0.8 V.
Now we need a multimeter, preferably set to the 2-volt range. The negative lead goes to
the VDD ground on the driver board (or on the Arduino). The positive test lead goes to
the (very small) screwdriver that we use to adjust the potentiometer. Then we turn it
until exactly 0.8 V is displayed, or, for 1.5 A, correspondingly 1.2 V.
For current settings above 1 A, however, a miniature heat sink should be mounted on
the A4988 chip. Many offerings come with these, usually with double-sided adhesive
tape. Even better is gluing it on using a drop of heat-conductive glue. But, be careful:
The potentiometer must remain free.
If several stepper motors and controllers are required for an application, some of the
input lines of all of the controllers tied together. Only the signals DIRECTION, STEP, and
ENABLE are required separately for each motor.
185
STP: With each change from LOW to HIGH at this input, the
next step or microstep is executed.
RST: This pin must also be HIGH during operation. A LOW signal turns off the motor
and resets the internal position.
186
EN: This line must be LOW for the motor to get power. It is best to use this line to
switch off the motor between runs with a HIGH signal here, while RESET and SLEEP
stay high.
GND: In the upper-right corner of Figure 9.2.4b is the ground pin for the input signals,
which we connect to the Arduino ground.
FLT: The VCC voltage from the Arduino is not applied to this output (as is the case with
the A4988). On the DRV8825 board, this is an output that is normally HIGH, but goes
LOW in the event of an error (if the temperature or current is too high).
1A, 1B: These are the output pins for the first coil of the motor.
GND: This is the motor voltage’s ground. It is best to connect the ground from the
Arduino to the other ground pin, but tie the two ground pins together anyway.
VMOT: The voltage for the motor is applied to the last pin (bottom-right in Figure
9.2.4b) (8.2 to 45 V). However, it is preferable to use only 12 V.
The reference voltage, which we have to measure and set, is also set directly using the
potentiometer, and here we also have to include a calculation factor, not 8 this time (as
with the A4988), but rather 5. The voltage we have to set is thus:
U=5IR
R is again the value of one of the two resistors. I is the current we want to set. Here,
too, no motor current flows during adjustment, because we are only setting a voltage
that will later be used to limit the current.
Example: If we want to set the current to 1.5 A, we calculate 5 1.5 0.1 = 0.75 V.
Now we need a multimeter, preferably set to the 2-volt range. The negative test lead
goes to the ground pin, which is connected to the Arduino ground. The positive lead
goes to the (very small) screwdriver, which we use to adjust the potentiometer. We now
turn it until exactly 0.75 V is displayed.
Again, a mini heat sink should be glued to the DRV8825 chip if the current is to be
above 1 A. Often, such mini heat sinks are supplied.
187
The brushless motor works the other way round. The coils are fixed and the magnet
rotates. This has the advantage that no sliding contacts are needed. Brushless motors
are therefore more durable, and are usually more efficient. Equipped with high-quality
magnets, even small motors can perform amazingly well. Drones, for example, almost
exclusively use such motors, as well as e-bikes, e-scooters and even electric cars.
One disadvantage is that brushless motors cannot simply be supplied with direct
current, as this would cause them (like a stepper motor) to simply remain in their
current position. Instead, the three coils need a kind of three-phase current, whose
frequency and phase position must be synchronous with the motor rotation.
188
Power supply
Such ESCs are mainly used for operation on rechargeable batteries, for example in
quadcopters. The permissible operating voltage is therefore often given as the number
of lithium-ion batteries connected in series, e.g. as “2 to 3S” or “2 to 4S.” This means
either 2 to 3 batteries in series or 2 to 4. When fully charged, such a battery cell has up
to 4.2 volts, and when almost empty, 3.5 volts. This results in a permissible voltage
range of 7 to 12.6 volts, or up to 16.8 volts with 4 batteries in series. For mains
operation, I recommend a 12-volt power supply, because that is always adequately
sized, unless you need even more power, in which case a higher voltage may be useful.
The connections
ESCs usually have a thick black wire for motor ground, and an equally thick red wire for
the positive pole. On the other side are usually three more thick wires connected to the
three motor wires. There is no order to follow, but only a very simple rule: If the motor
must run backwards simply swap two of the three wires – it doesn’t matter which two.
189
To control the ESC there is a thin wire with three strands, usually red, black (or brown),
and yellow (or white). Red and black are plus and minus for a small 5V voltage, which
the ESC generates internally. Here, for example, a Pro Mini board could be connected
directly to VCC (or RAW). If our Arduino already has its own power supply, we don't
connect the red wire at all, but only the black ground wire to the Arduino ground. The
yellow (sometimes also white) signal line is connected to one of the board’s outputs.
Control signal
ESCs are controlled using regular pulses on the control line. This method has been used
for decades for remote-control model cars and aircraft. Servos (e.g. for steering) are
also controlled using such a signal.
The pulse frequency may be between 50 and 500 Hz. It is best to do 100 pulses per
second, or slightly more. The frequency is actually unimportant. Control depends on the
duration of the pulses, which should normally be between 1000 and 2000 µs. At
1000 µs, the motor is off. At 2000 µs, it runs at maximum power. In between, we can
control it from 0 to 100 percent.
The following small example program converts the measured value of a potentiometer
into pulses, so that we can use the ESC to control the motor via the potentiometer.
#define signal_pin 2
#define pot_pin A0
void setup()
{
pinMode(signal_pin, OUTPUT); // set signal pin to output
digitalWrite(signal_pin, LOW);
}
First the constants for the I/O pins used are defined here – an output pin for the ESC
signal and an analog input pin from the potentiometer. Then come the variables
“power” for the motor power, pot_16 for the analog measurement and a variable for
counting. In setup(), only the signal pin is initialized.
190
void loop()
{
pot_16 = 0; // reset
for (n=0; n<64; n++)
{
pot_16 += analogRead(signal_pin);
}
power = pot_16 >> 8; // to 8 bit (0-255)
digitalWrite(signal_pin, HIGH); // start pulse
delayMicroseconds(1000);
delayMicroseconds(power << 2); // 0 to 1020
digitalWrite(signal_pin, LOW); // finish pulse
}
In loop(), the pot_16 variable is first set to 0 and then the analog signal is read and
summed 64 times. The 10-bit values thus become one large 16-bit value (actually up
to a maximum of 65,472). This increases the accuracy, but this is not really necessary
here. The main reason is that 64 measurements take a few milliseconds, so that we
get the correct time interval for the single control pulses that we send to the ESC.
The 16-bit value of pot_16 is now shifted 8 bits to the right (i.e. divided by 256), so
that we get an 8-bit value (0 to 255) for the “power” variable. Then, the signal pin is
set to HIGH, so the pulses are activated. The 1000-microsecond delay defines the
minimum pulse duration. In addition, in the second delay, the power value is shifted
two bits to the left (i.e. quadrupled), which corresponds to a further delay of 0 to
1020 microseconds. Then, the pulse is terminated using a LOW level before the loop
starts measuring again.
The schematic for ESC motor-control using a potentiometer looks like this:
Here we can use an 8 or 16 MHz Pro Mini board, or even a LilyPad, because we don’t
even need a voltage regulator onboard if we use the 5 V supply from the ESC.
191
9.4 Servos
Servos are used wherever small
mechanical movements of a few
millimeters or a few centimeters
are required. The adjustable arm
can be controlled back and forth
by half a turn. Using a stiff wire
extension that is hooked into one
of the holes, we can transmit the
movement to wherever we need
it. Due to their internal gears,
servos have a lot of force.
Control
Control is exactly the same as with the ESC, with pulses of between 1000 and 2000 µs,
which are sent via the yellow control line. At 1500 µs, the lever is in middle position. We
can even use the same potentiometer control program that we saw in the previous
section (9.3.1).
Again, the black (or brown) wire is the ground wire, while the red wire is 5 volts, except
that here (unlike on the ESC) the red wire does not supply 5 volts, but requires 5 volts
to power the servo. Since this is only a very small motor that doesn’t need much
current, we can power the servo via the Arduino’s VCC voltage.
192
Chapter 10 • Sensors
There are a variety of different sensors that can be used to collect all kinds of data,
which we can then process and evaluate with the Arduino. Temperature, brightness,
motion, pressure, and ultrasound are just a few examples.
The simplest examples of such variable resistors are the LDR (light-dependent resistor)
and the NTC (temperature-dependent resistor, or thermistor), but even a simple poten-
tiometer could be considered an analog sensor – a sensor that measures rotational
position.
193
We do not need to pay special attention to anything when buying, as any LDR can be
used. Larger LDRs are usually lower impedance overall and often cost a few cents more,
but a small LDR will measure just as well if we make the fixed resistor in the voltage
divider of a correspondingly higher impedance. The best way is to measure the
resistance of the LDR (with a multimeter) at about the brightness we want as the
threshold value, i.e. at which we want it to switch. If we take the same value for the
voltage divider as a fixed resistor, it will fit in any case. The ideal threshold value is then
roughly in the middle range, i.e. at about half the operating voltage.
Depending on the material composition of the NTC, the measurement curve (how
much the resistance changes with temperature) can be very different. The most
important parameter (besides the nominal resistance at 25 degrees) is the
temperature coefficient B 25/50, which is usually specified as a 4-digit number. It is
often 3950, and the following sketch is designed for such NTCs. We can freely choose
the nominal resistance of the NTC and the fixed resistor in the sketch. It makes sense
to use an NTC of 10 kΩ or more, or better 50 kΩ, and a similar fixed resistor (i.e.
10 kΩ or 47 kΩ). The nominal resistance of the NTC should only have a tolerance of
1% if the measurement is to be accurate – as should the fixed resistor in use.
Strictly speaking, there are also slight differences in the characteristic curve of the
3950 types (depending on the manufacturer and other designations), but we can
neglect them here. Our measurement should be accurate to within a few tenths of a
degree in the range of 10 to 40 °C (i.e. near 25 degrees).
If we want to measure the room temperature, the NTC should not be too close to the
Arduino, because it heats up nominally. With longer connection lines to the NTC (more
than 10 cm, especially if it is also relatively high impedance) I recommend at the
board’s analog input a capacitor of 100 nF connected directly to ground.
First, we define the pin to which our voltage divider (consisting of fixed resistor and
NTC) is connected. Then comes the variable section, which we can adjust. The number
of measurements is of course much smaller for applications that have more to do than
just displaying the temperature. If necessary, even a single measurement would
suffice. The large number of 10,000 measurements here is essentially just to kill time
between outputs.
Next, the resistances in the voltage divider must be entered – the nominal resistance
of the NTC and the fixed resistor used, which are ideally about the same. The
r_factor[] array contains the measurement curve of such 3950 NTCs, strictly speaking
the deviation factor from the nominal value, at temperatures from -30 to 130 °C in
5-degree increments. The 1 at the end of the upper number row is the value for
25 degrees, because there is no deviation and thus the factor is 1.
195
For some NTCs a data sheet is also available, which has this information (like here in
5-degree steps). If you have the corresponding data from your NTC, you can enter the
range from -30 to 130 degrees here. This way, the result may be even more accurate,
and NTCs that are quite different may be used.
These are the other variables used by the program. It’s almost identical to the old
sketch for resistance measurement from chapter 6.2.5, which was adapted and
extended for this. New are the variables “present_factor” and “temperature.” “n” is
used later to count through the array to find the position between which two entries
the measured value lies.
void setup()
{
pinMode(measure_pin, INPUT); // set measurement pin as input
Serial.begin(38400); // set up serial transmission
}
There is nothing special in the setup. Here only the measuring pin is defined as input
and the serial transmission is started.
void loop()
{
counter++; // count
sum += analogRead(measure_pin); // sum new measurement
if (counter == amount) // if total number is reached
{
ratio = sum / float((1024 * (long)amount) - sum); // ratio
rm = r_fixed * ratio; // NTC resistor (when NTC is connected to ground)
//rm = r_fixed / ratio; // NTC resistor (when NTC is connected to VCC)
In the loop, the measured values are first summed up. If the number “amount” is
reached, the resistance is calculated. The lowest line (commented out) can be used
instead of the previous one if the fixed resistor is connected to ground and the NTC to
VCC, but this is rather unusual.
196
{
Serial.println(""); // blank line
Serial.print("Error!");
Serial.println("NTC missing or too cold!");
}
else if (present_factor < r_factor[32])
{
Serial.println(""); // blank line
Serial.print("Error!");
Serial.println("NTC short-circuited or too hot!");
}
else // valid measured value
{
The currently measured resistance of the NTC is now divided by its nominal value and
thus the present_factor is calculated. The temperature can thus be calculated from the
array. Before this, however, it must be ensured that the value is within the permissible
range (from -30 to 130 degrees), otherwise the calculation would attempt to access
array values that do not exist. Therefore, in the event of an error, appropriate
messages are output and the calculation is only executed if the value is valid.
n = 1;
while (r_factor[n] > present_factor) n++; // till array value is no longer greater
temperature = 5 * (n - 7); // assign step of 5 degrees
// Add intermediate value (between array steps):
temperature += 5 * (r_factor[n-1]-present_factor) / (r_factor[n-1]-r_factor[n]);
Now the array is searched until the first entry is no longer greater than the current
value. However, “n” does not start with 0 but with 1. (Otherwise, at a temperature of -
30 degrees, the array index no. -1 would be called, which of course does not exist).
197
Now the measured value and the temperature calculated from it are output. Then only
the counter for the measurements and the summed measured value are reset. A delay
is not necessary (thanks to 10,000 measurements).
With some NTCs, instead of the factor of the deviation (from the 25° nominal value),
you will find the complete resistance value in the data sheet in the listing of the
resistance values at different temperatures – so at 25° there is not 1, but the nominal
value. We can also adjust our r_factor[] array with such values. In return, we only have
to enter 1 as r_ntc25 instead of the nominal value. Or, we enter the value 1000 and in
the array the information in kiloohms.
198
Then we have three outputs, which we connect accordingly to input pins of the Arduino.
For the X-direction (left and right) we need an analog input, likewise for the Y-direction
(up and down). Then there is a push-button that is activated when we push the stick in.
For this, a digital input is sufficient. The button switches to ground. We can activate the
necessary pull-up resistor within the ATmega chip via software.
Such a remote control signal consists of individual pulses and short pauses
whose durations are roughly in the range of a millisecond. But it can also be
several milliseconds, or a little less, because these temporal variances
contain the actual information.
Fig. 10.2.1a: VS1838B →
199
To distinguish the infrared signal from other light sources, the remote control modulates
the signal at 38 kHz. This means that no continuous pulses are sent. Instead, the
remote control switches its infrared LED on and off 38,000 times per second during the
pulse duration. (There are also remote controls that use a different frequency, but they
are extremely rare).
Fig. 10.2.1a: Infrared remote control – modulation and demodulation of the signal
This component now detects the frequency of 38 kHz and reconstructs the actual data
pulses and pauses from it (with a small delay). The picture shows the original data
signal in the upper graph. The middle graph is the modulated signal as sent by the
remote control, and the bottom graph shows what the receiver makes out of it again.
So we get pretty much exactly the original data signal. The small delay is inevitable,
because at the beginning of each pulse the receiver must first detect whether it is
actually a 38 kHz signal.
We connect the TL1838 or the nearly identical VS1838B (which can be bought
in a pack of ten for a few cents) to ground and positive (VCC), and to one of
the Arduino’s digital inputs, which we then switch to input with an internal
pull-up resistor.
The infrared receiver can also be used on the 8 MHz version of the Pro Mini and for
battery-powered devices. It also works perfectly on 3 volts. The question is rather to
what extent battery operation makes sense at all, because the component always
consumes a minimal current (in the order of about 1 mA), and if we build it in such a
way that the board can be switched to standby by remote control (as in section 13.2),
200
we must not switch off the receiver if we want to be able to switch the device on by
remote control. Otherwise we could connect the positive pole of the sensor to an output
of the Arduino instead of VCC. This would also allow us to switch off the receiver.
By the way, with this infrared sensor there is another very interesting (yet very easy to
build) project starting on page 291 in chapter 13.7. With it, we can even give a second
life to old infrared remote controls to turn on the room lighting or other things.
Its use could hardly be simpler. Besides ground (GND) and the positive pole (VCC) there
are only two more pins, the input (Trig) and the output (Echo). Distance measurement
is triggered by a short pulse on the input (Trig). The sensor then emits a short
ultrasonic signal and waits for the echo. At the same time, it sends out a pulse on its
output pin (Echo), which goes inactive again on reception of the echo. The Arduino only
has to measure the duration of the pulse and can then determine the distance based on
the speed of sound. The calculated distance has to be halved afterwards, because the
sound went there and back – so it covered twice the distance.
201
duration = pulseIn(5, HIGH, 1000); // wait for HIGH impulse (max. 1 ms)
The pulseIn() function waits for a pulse at the specified pin with specified level - so
here for a HIGH pulse at Pin 5. Then the function waits for the end of the pulse (so in
the example back to LOW level) and then returns the pulse duration in microseconds.
The third (optional) value gives the maximum wait time in microseconds. This may
also be a long value. If no duration is specified, the function waits a maximum of
1 second. If the pulse is not completed within the specified time, the function returns
0, which can also be evaluated as false.
We can connect the two signal lines of the sensor to any of the Arduino’s input/output
lines. In the first two lines, the connections are defined. In the example, these are
pins 2 and 3.
Then we need two variables, echo_time for the time in µs to wait for the echo, and
“distance” for the converted distance value in cm.
202
void setup()
{
pinMode(trig_pin, OUTPUT); // set trig pin to output
digitalWrite(trig_pin, LOW); // and to LOW
pinMode(echo_pin, INPUT); // // Set echo pin to input
Serial.begin(38400); // set up serial transmission
}
There is not too much to do in the setup. Here only the inputs and outputs are set and
the serial transmission is prepared.
void loop()
{
digitalWrite(trig_pin, HIGH); // start pulse
delayMicroseconds(10); // wait a short time
digitalWrite(trig_pin, LOW); // end pulse
echo_time = pulseIn(echo_pin, HIGH, 29138); // wait for echo (limited to app. 5 m)
if (!echo_time) // if no echo was received during the time
{
Serial.println("Distance too large, not measurable!"); // error message
}
loop() starts the measurement immediately. A high signal is applied to the trig_pin for
10 microseconds, which initiates the measurement. The sensor then sends a short
ultrasonic signal of 40 kHz and switches its Echo output to HIGH until the echo is
received.
The pulseIn() function now waits for the pulse and measures its duration, limiting the
wait to 29,138 µs (just under 30 ms). (After all, in most programs we don't have
forever, and this waiting time already corresponds to a distance of 5 meters – more
than we can probably measure). As a result, the pulseIn() function returns the time
until the echo, or 0 if our time limit has expired. In this case (if the variable echo_time
is 0), a corresponding message is output that the distance is not measurable.
203
Otherwise (if an echo was received), the time is converted into the corresponding
distance (in cm) and output serially. The divisor, 58.275, results from the reciprocal of
the speed of sound (343.2 m/s at 20 °C) multiplied by 2000. The factor 2000, in turn,
results the fact that we measure twice the distance (there and back) and that we have
to convert m/s into cm/µs.
The final delay determines the cycle in which the measurement is done every second,
and the distance is output serially. In a real application (where distance is used to
control something), we have better things to do than wait for an entire second.
Instead, we could take more than 30 measurements per second, or even up to 50 if
we reduce the maximum waiting time from 29,138 to 19,000 µs. With this, distances
of up to 3 meters can theoretically still be measured, and more would hardly be
possible anyway.
Sensitivity
How far we can measure distances with the HC-SR04 depends on many factors. The
sensor is most sensitive towards at the front, with a narrow angle of 15 degrees.
Obviously, close objects (e.g. under 1 meter) can be detected more easily than things
farther away. In addition, a smooth surface perpendicular to the direction of
measurement is naturally easier to detect than an angled surface or a small and/or soft
object. A pencil, for example, is still easily detected at a distance of 30 cm. A large,
bare wall perpendicular to the direction of measurement could probably still be detected
from more than 3 meters away.
204
If necessary, the HC-SR04 can also be used in battery-powered devices with an 8 MHz
clock frequency. However, the lower the operating voltage, the lower the sensitivity and
the measurable distance. The lower clock frequency also has a small disadvantage,
because it reduces the temporal resolution with which the echo delay is measured, but
the error is only in the order of 1 mm.
Such motion detectors are excellent in conjunction with Arduinos. They’re available very
cheap today, and the best version—with adjustable time and sensitivity—is often
available at the lowest price. I’m referring to the HC-SR501, which can sometimes be
bought from China for as little as one euro.
The biggest advantage of the HC-SR501 is its low power consumption: only 65 µA
(sometimes also specified as 50 µA). Therefore, we can use e.g. the standby mode of
the Arduino (which I explain later in section 13.2) and thereby wake the Arduino up via
the OUT pin of the motion detector in case of motion only. This way, a corresponding
circuit can run on battery power for months (e.g. with an 18650 lithium-ion battery) if
the Arduino is in standby mode most of the time, since the motion detector alone
consumes very little power.
205
The HC-SR501 outputs a HIGH signal when motion is detected. For many applications
we can connect the output directly to an Arduino input, as shown in the left picture
above. But if we want to use the standby mode of the Arduino, we have to invert the
signal, as shown in the right picture, because for waking up we need the change from
HIGH to LOW. Otherwise, the Arduino would wake up only when the signal is turned off.
The standby mode and how to wake up the Arduino is described in detail in
chapter 13.2.
To make the adjustments, we first turn both pots all the way to the left (i.e.
counterclockwise). This way we have the lowest sensitivity and the shortest time. Now
we can test how high the sensitivity should be. However, we should possibly not turn
the potentiometer all the way up, i.e. not all the way to the right stop, because
otherwise even small disturbances can trigger a signal.
It’s best to leave the potentiometer for time setting completely on the left stop, because
the exact time - e.g. how long a lamp should remain switched on - can be set much
better and more precisely with the Arduino. An exact time setting via the potentiometer,
on the other hand, is very difficult and inaccurate.
206
207
The picture shows 3 devices with such a connection line. The left transistor can pull the
signal to LOW (to ground). If none of these transistors is conducting, the pull-up
resistor pulls the signal HIGH (VCC). However, each device is also an input and can read
the signal, which is symbolized by the right transistor in each case. (Of course, these
transistors may only load the signal minimally, because a HIGH level from the pull-up
resistor must of course remain HIGH).
With the I²C interface there are now two such lines with pull-up resistor – SCL and SDA.
In contrast to the serial interface, which we already got to know in chapter 2 (RS-232 or
UART), no lines are swapped here. Instead, all SCL connections are always connected to
each other, and likewise all SDA, regardless of whether 2 or 10 devices are connected.
One of the devices (usually the microcontroller) always has the role of the master. It
sets the clock on the SCL and manages the communication, which takes place according
to certain rules.
Let's imagine the blocks as individual hotels, with room numbers and guests. So that
such a block knows when it is meant, each has its own address, so to say the house
number, which is called first. Then the corresponding register of the block is addressed.
These are memory locations that can be read and/or written, so to say the individual
rooms. The guests living in them, are the bits or the byte that the microcontroller now
writes into it, or requests, in the last step of the communication. In case of a request,
the device then sends the desired byte to the microcontroller.
208
This example includes a library named Wire.h, which provides additional functions.
This library (like many others) is already included in the Arduino installation. You only
have to include it using this line and you can use it directly.
There are many such libraries for many special tasks, even very special ones that you
only find somewhere on the internet and have to install first to be able to use them.
We don’t even have to know or understand the functions they contain. It is usually
enough if we know what they do.
209
To use the I²C functions we have to include Wire.h at the beginning. Then we define
the 7-bit address of the device we want to communicate with (in the example it is a
BMP180 air pressure sensor). Actually the address has 8 bits, but the last bit always
stands for read or write only, and Wire.h sets that automatically. So the 7-bit value is
half of the 8-bit address, if you round down. The 0x in front of the number means
hexadecimal notation, which is usual here. You could have written 119 instead of
0x77, but that would be just too easy.
Wire.begin();
This line is always in setup(), because it is only necessary to set the transmission up
once. With empty parentheses, the Arduino is the master. If one were to put a 7-bit
address in the parentheses, the Arduino would be a slave and could be addressed by
the master with this address. This way 2 Arduinos could communicate.
In this example we call the register 0xF4 from the sensor and write the value 0x2E
into it (which, for this sensor, means that we want to measure temperature).
Now we have again accessed a register (0xF6), but without writing anything into it,
because we now read this register (and the following ones).
Here we first specify that we want to read 2 bytes from the current register. Then, to
be on the safe side, we wait until the corresponding byte is available before we read it
and combine it as an integer value. (In this case it is the measured temperature
value, but not yet in degrees, but the raw data).
210
I²C sensors
There are many sensors that communicate via I²C interface. I would like to introduce a
few of them now. However, it would go beyond the scope of this book to deal with all
registers, setting these sensors’ options and operating modes. If you want to do that,
you will find all information in the respective data sheet. All you have to do is search the
internet for the name of the sensor in conjunction with the term “datasheet.” This way
you’ll usually find a PDF from the manufacturer very quickly. Here in the book we limit
ourselves to the essential basic settings. In most cases, it is sufficient if the sensors
provide us with the desired data.
Breakout boards
Fortunately, the common sensors, which are sometimes only 2 or 3 millimeters in size,
are available nicely pre-assembled on small cards with a pin header for connection.
Such mini boards are also called breakout boards. They are mainly used to simplify the
connection of a sensor or other ICs. In principle, you could also call the Arduino itself
(at least the Pro Mini) a breakout board, because it essentially only makes the
connections of the microcontroller available and otherwise has only a few components.
The sensor is usually offered on a tiny circuit board with 4 or 5 connections. The small
metal can package (next to the mounting hole in the picture) is the actual sensor. The
black component above the “M” and the “P” is a 3.3-volt regulator that supplies the
sensor with its preferred voltage. The two required pull-up resistors can also be seen,
top left. Their inscription, 472, means 47 with 2 zeros, so 4700, or 4.7 kΩ.
211
So, we can connect the sensor directly to the Arduino. The easiest way is to use a
jumper cable (as described on page 44), but for a permanent setup I always
recommend soldering the wires. The SDA connector goes to A4 and SCL to A5. Further,
VCC (sometimes VIN) goes to VCC, no matter if we have 5 volts or less there, and GND
goes to GND of course. There may be a 3.3 V pin at the sensor, but this is not used.
We need:
212
In the sketch we now use self-defined functions, rather than just setup() and loop().
Thus, a few explanations first:
This small example defines a function called switchOff(), which sets several outputs to
LOW at once. This is handy if you need to use the same program lines again and
again at different places in your sketch - you only need to define the function once,
and then you can replace these 4 lines throughout with the single instruction,
switchOff().
float multiplyBy10(float x)
{
return 10 * x;
}
This is a simple example of a function with pass and return values. The “float x” in
parentheses indicates what kind of variable is passed to the function, and under what
name it is available in the function. The empty parentheses we’re accustomed to
simply indicate that no value is passed. It is also possible to pass several values to a
function, which are then separated by commas.
It is also noticeable that instead of the usual void here, we have float before the
function name, because this definition indicates which data type the function returns,
and void would mean that there’s no return. A function with a return usually ends
with the return keyword followed by the return value (here, 10 * x).
There are also functions that have either only one variable passed or only one return.
For example, our next sketch uses only one return value.
213
After including Wire.h for the I²C communication, the 7-bit address of the BMP180
sensor is defined as a constant called “address.” The prefix 0x indicates that the value
(77) is specified in hexadecimal notation. Then, the required variables are defined. For
“oversampling,” we use the values 0 to 3 to specify whether 1, 2, 4, or 8
measurements are taken at a time, respectively – more measurements increase the
accuracy somewhat. The array indicates how long we have to wait for the
measurement result for each of the different oversampling settings. The other
variables have been separated by commas, because this allows us to define several
variables of the same type at the same time.
void setup()
{
Wire.begin(); // prepare I2C transmission
In setup(), the I²C transmission is set up as master with Wire.begin(). Then, the
sensor is set to register 0xAA, also specified in hexadecimal notation.
214
Now, 22 bytes are read from the previously specified address (0xAA) and 11 16-bit
integer variables are formed from them. The actual reading of 2 bytes at a time is
done using the read_int() function, which is located at the end of the sketch. The
values are individual calibration data, which the manufacturer of the BMP180 has
stored there for us. We need this data later to determine the exact temperature and
the exact air pressure from the measured values. For control, this data is then also
output to Serial Monitor.
void loop()
{
get_t(); // read temperature value
get_p(); // read pressure value
calculate(); // calculate temperature and pressure from raw data
altitude = 4433000 * (1-pow(((float)pressure/101325),1/5.255)); // height in cm
In loop() the get_t() and get_p() are called to read the values for temperature and air
pressure. Even if only the pressure is needed, the temperature measurement is
important, because both values are just raw data, and to calculate the pressure
exactly, the temperature value is also necessary. This is done in the next step. The
calculate() function determines the exact temperature and pressure, with the help of
the 11 calibration variables, and, in the next line, the altitude is calculated.
215
The data is then output serially and can be tracked in Serial Monitor. The temperature,
in tenths of a degree, is converted to degrees, and the pressure is converted from
Pascal to Hectopascal, which also corresponds to the “millibar” unit. The delay at the
end sets the output clock to one second.
Now for the function for reading the raw temperature data. First, the Control register
(0xF4) is selected, and the value 0x2E is written to it, which means that we want to
measure temperature. Then, a delay of 4.5 ms is applied, because this is how long the
temperature measurement takes until the result is available. Then, Register 0xF6 is
selected, because the result is written to registers 0xF6 and 0xF7. So, 2 bytes are
requested starting from the current address. To read them, the read_int() function is
again used.
The get_p() function is similar to get_t(), but it reads the air pressure data. Again, the
Control register is selected first, but the value that is written into it now depends on
216
our oversampling setting, because the sensor does the oversampling internally.
Likewise, the delay time is also a variable now, the value of which we take from the
array according to the oversampling setting. This time we create the delay using the
system time with an empty while loop. The “32760 &” ensures that only the lower 15
bits (32,767) are considered, but excluding the last 3 bits. So, you can calculate using
int variables, and the microseconds are only evaluated at each 8 th step, because the
system time (at least at 8 MHz clock frequency) is not more accurate than this in any
case.
Finally, the result is read again from Register 0xF6, but this time as a 3-byte value.
Therefore, the first byte is read first and shifted left by 2 bytes. For the remaining two
bytes, the read_int() function, in conjunction with an OR operation “|=” is used again.
At the very end, the result is shifted according to the oversampling setting, so that a
16- to 19-bit value results.
void calculate() // calculate temperature (in tenth degrees) and pressure (in pascal)
{
x1 = (((long)t - ac6) * ac5) >> 15; // calculate temperature
x2 = ((long)mc << 11) / (x1 + md);
b5 = x1 + x2;
temperature = (b5 + 8) >> 4; // temperature (in tenths of degrees)
b6 = b5 - 4000;
x1 = (b2 * ((b6 * b6) >> 12)) >> 11;
x2 = (ac2 * b6) >> 11;
x3 = x1 + x2;
b3 = ((((long)ac1 * 4 + x3) << oversampling) + 2) >> 2;
x1 = (ac3 * b6) >> 13;
x2 = (b1 * ((b6 * b6) >> 12)) >> 16;
x3 = (x1 + x2 + 2) >> 2;
b4 = (ac4 * (unsigned long)(x3 + 32768)) >> 15;
b7 = ((unsigned long)p - b3) * (50000 >> oversampling);
if (b7 < 0x80000000)
pressure = (b7 * 2) / b4;
else
pressure = (b7 / b4) * 2;
x1 = (pressure >> 8)*(pressure >> 8);
x1 = (x1 * 3038) >> 16;
x2 = (-7357 * pressure) >> 16;
pressure += (x1 + x2 + 3791) >> 4; // pressure (in pascal)
}
This function now calculates the exact temperature in tenths of a degree and the
pressure in Pascal from the raw data, “t” (for temperature) and “p” (for pressure),
according to the manufacturer’s specifications. The function uses the 11 calibration
variables, as well as 8 additional temporary variables (x1 to x3 and b3 to b7). All these
calculations (from the data sheet) cannot and need not be understood – no, really!
217
unsigned int read_int() // read two bytes as an integer from the I2C bus
{
while(!Wire.available()); // wait
unsigned int int_register = Wire.read()<<8; // higher significant byte comes first
while(!Wire.available()); // wait
int_register |= Wire.read(); // lower significant byte
return int_register; // return value
}
This is the read_int() function, which reads two bytes that have already been
requested and combines them into an integer value. The two empty “while” loops wait
only if necessary to ensure that the respective byte is available.
First, the measured temperature is displayed on each line. Then, the raw air pressure
reading, which is not very meaningful, because the air pressure must first be calculated
from this. That’s the next value. The last value is the calculated altitude (above sea
level), which can never be exactly correct, because the current weather is always a
factor, so a few meters will be added or subtracted under low pressure or high pressure,
respectively.
218
As you can see, the measured height also fluctuates by up to one meter with each
readout. This may be due to weather, on the one hand, as every gust of wind brings a
small change in air pressure, while, on the other hand, there are also slight inaccuracies
that exist with every measurement.
So, we still have a lot of time left, in which the program always just waits for one
second. We could use this time better, with a renewed oversampling of the already
previously-sampled values, and we have a sketch for that in section 13.8. There, the
current height change is then also indicated in centimeters, so we can recognize height
differences starting from as little as 30 cm.
219
Even more astonishing is what this 2-euro component has integrated within it. Besides
the actual sensors with numerous setting options, there’s an integrated digital motion
processor (DMP) that could be used, as well as a second I²C interface with which this
sensor could read from other sensors, 1 kilobyte of intermediate memory (FIFO), and
much more.
All this would go far beyond the scope of this book. Therefore, we will limit ourselves
here to setting the measuring ranges and reading and outputting the data.
We need:
#include<Wire.h>
#define address 0x68 // I2C 7bit address of the MPU-6050
int accel_x, accel_y, accel_z, temp, gyro_x, gyro_y, gyro_z; // sensor data
float ac_x, ac_y, ac_z, temperature, gy_x, gy_y, gy_z; // converted data
As usual with I²C communication, Wire.h is first included and the device address
defined. Two variables, ac_range and gy_range, specify the measurement range for
the acceleration and rotation force. The unit, “g,” stands for gravity (9.81 m/s²), and
rotation speed is specified in degrees per second. Then the corresponding variables in
X, Y, and Z direction (as well as a temperature variable) follow first as ints for the
measured raw data and then as floats for the corresponding calculated values.
void setup()
{
Wire.begin(); // prepare I2C transmission (as master)
220
In the I²C transfer, register 0x1B of the sensor is addressed first and the _range value
(shifted left three bits) is used to specify the rotational measurement range. The
acceleration measurement range is entered into the next register (0x1C). Afterwards,
the sensor is activated with a value of 0 in register 0x6B. The “true” values at the end
of the transmission are optional and may also be omitted. They only mean that the I²C
bus must be released afterwards. Then, we set up the transmission to Serial Monitor.
Here we could also increase the transfer rate, maybe even up to 500,000 baud. This
creates a faster display at the output, so that the text doesn’t jump around so much.
void loop()
{
Wire.beginTransmission(address); // address the MPU-6050
Wire.write(0x3B); // first output register
Wire.endTransmission(false);
Wire.requestFrom(address,14,true); // request 14 bytes (7 int values)
accel_x = Wire.read()<<8|Wire.read(); // acceleration X
accel_y = Wire.read()<<8|Wire.read(); // acceleration Y
accel_z = Wire.read()<<8|Wire.read(); // acceleration Z
temp = Wire.read()<<8|Wire.read(); // temperature value
gyro_x = Wire.read()<<8|Wire.read(); // rotation X
gyro_y = Wire.read()<<8|Wire.read(); // rotation Y
gyro_z = Wire.read()<<8|Wire.read(); // rotation Z
In loop(), first the area with the output values is specified, which starts with register
0x3B. From here on, the 14 bytes or 7 integer values are read out. Here, too, the
“true” and “false” parameters are not mandatory, and only indicate whether the bus is
to be released (for other communication).
221
Now the data (7 integer values, which may also be negative) that was read is
converted. For temperature, there is a formula from the MPU-6050’s datasheet. The
remaining 6 variables are each based on the selected measuring range with their
16-bit range of ±32,768. Example: In the ±2g range, the maximum value of 32,768
corresponds to an acceleration force of 2g (even though, strictly speaking, it only goes
up to 32,767 in the positive range).
delay(500);
}
Now, the values are printed. The \n character that we’ve used in some lines means
new line, like Serial.println() does automatically, but with the difference being that with
\n you can start a new line at any place in the output, not just the end. The delay() at
the end of the loop determines the time between which outputs take place.
Output window
Serial Monitor output will look similar to picture. It is normal that minimal rotations are
displayed even when there is no movement at all. One could re-calibrate by subtracting
these values (here -19, 12 and -40) from the measured value. You might also notice
that the sum of all “g” values does not add up to 1 (as you might expect) but to 1.19.
This is simply due to the fact that the spatial dimensions are at right angles to each
222
Project: 3D compass
A compass can be created using the HMC5883L sensor’s measured values. In the
process, we discover that Earth’s magnetic field runs quite differently than is commonly
imagined – by no means simply horizontally from north to south, but (depending on the
latitude on Earth) more or less steeply upwards, and even vertically at the north and
south poles. In contrast, the magnetic field lines are only horizontal in the area of
the equator. In central Europe, the magnetic field points steeply upwards in the
southern direction at an angle of about 60 degrees, and steeply downwards in
the northern direction. We can also measure this angle—the so-called inclination—with
the 3D compass.
We need:
223
As usual with I²C transmission we first have to include Wire.h and define the device
address as a constant. Then, the variables are defined: int types for the raw data, long
types for adding up and floats for the converted values.
void setup()
{
Serial.begin(38400); // prepare serial transmission
Wire.begin(); // prepare I2C transmission
In setup(), the two serial interfaces are set up first – the connection to the PC and the
I²C bus to the sensor. In Register 0 of the sensor (Configuration A) the measuring
frequency is determined. In the next register (1 or Configuration B) the sensitivity is
set. We can also replace this line with one of the two commented-out lines to change
the sensitivity. However, we must then also adjust the factor later during the
conversion. Furthermore, only one of these lines may be active at a time. Otherwise,
the other values will end up in the wrong register, because after that comes the value
for Register 2 (Mode Register). This sets the sensor to continuous measurement.
224
void loop()
{
x_added = 0; y_added = 0; z_added = 0; // reset
In loop(), the actual measurement is in a for loop that is executed 64 times. The delay
of 13 milliseconds gives the sensor the necessary time to provide the next measured
values. The raw values for the X, Y, and Z directions are then read from Register 3 as
integers and added to the _added variables, which were reset to 0 before the loop
began.
225
Here, the measured values are now first converted into µT (microtesla). For this, a
conversion factor (according to the setting) is required. In addition, the summed value
must of course be divided by the count of summations (64).
Then, the direction of the magnetic field is calculated from the horizontal values (X and
Y). atan2() is a convenient function that calculates the angle from -180° to 180° from
the two coordinates. M_PI is a fixed constant, and simply means the number, Pi.
Together with the factor of 180, the conversion is done, so that one full revolution
gives us 360. The next line adds another 360 if the angle is negative. So, we get
values from 0° to 360°.
“dip” is the inclination, i.e. the vertical angle. For this, the atan2() function needs the
vertical magnetic field, z, as well as the horizontal magnetic field, which is calculated
from x and y. Since the dimensions are at right angles to each other, x and y cannot
simply be added. We have to add the squares of x and y here instead, and get the
square root from the sum.
Serial.print("X: ");
Serial.print(x);
Serial.print(" µT Y: ");
Serial.print(y);
Serial.print(" µT Z: ");
Serial.print(z);
Serial.print(" µT Direction: ");
Serial.print(angle);
Serial.print("° Inclination: ");
Serial.print(dip);
Serial.println("°");
}
At the end of loop(), the values are output – first, the converted X, Y, and Z values.
Then, of course, the direction or angle that the sensor deviates from north-south
orientation. The last value, the inclination, indicates the vertical angle, i.e. how steep
the magnetic field is.
226
Output window
This is what the output from the 3D compass looks like in Serial Monitor. The value
“Direction” indicates the horizontal angle from the north-south orientation. In addition,
“Inclination” shows how steep the magnetic field is. At the North Pole this value would
be 90°, in Germany still over 60°, and at the (magnetic) equator, 0°. In the southern
hemisphere, the value would be negative, down to -90° at the South Pole.
Fig. 10.2.8: GY-87
multi-sensor breakout board
227
For this purpose, there are cheap RF (radio frequency) remote controls with matching
receivers available from China. They work on frequencies of 315 or 433 MHz. The most
common and very cheap type is this version with 4 push-buttons.
At AliExpress, you can sometimes get these for 3 euro. It’s best to search there for
“2262/2272” – an identifier that refers to the ICs used. Otherwise, it may help to search
for “4-key remote receiver.” There’s just one catch: You usually find the 315 MHz
version – a frequency that, in contrast with 433 MHz, is actually reserved for military
use in Europe. If you want to use these, it’s best to observe the 11 th Commandment:
“Don’t get caught!”
The connection between the receiver and the Arduino is very simple. Aside from the
5-volt positive, ground must also be connected. Then, there are 4 signal wires, which
the receiver sets HIGH whenever the corresponding push-button is pressed. The fifth
line reacts to all push-buttons.
228
Coding
We can also use several of these remotes independently of each other, as the
transmitter/receiver signal pairs can be coded using 8 bits, and every receiver will react
only to a transmitter with the same Code.
Fig. 11.1b: Example of solder points for coding the remote control and receiver board
On the transmitter and receiver boards, there are 8 free pins at the IC. Above and
below them are two solder strips: above for positive (HIGH) and below for negative
(LOW). We can code the signal here by soldering the individual pins to positive or
negative, respectively. We need to be careful not to short circuit any of them, causing
the positive side to be connected to the negative side. The figure shows an example of
how the coding may look, wherein the solder connections are symbolized by red dots.
In this example, the code is 10011110.
Antenna
The receiver board has a solder point right in the corner, labeled “Ant.” Here, we solder
a piece of wire that serves as antenna. The best is a straight, stiff wire with a length of
23.0 cm for 315 MHz, or 16.5 cm for 433 MHz. It won’t do any harm if the length is a
bit different or if the wire is not exactly straight. It’s best to lay the antenna on the edge
of a device, where it is separated from the other signal lines. For metal enclosures, the
wire must be led to the outside.
The small telescopic antenna on the transmitter can usually be left retracted. To
increase the range, you can extend it. For even more range, the ground wire of the
transmitter (negative battery terminal) can be led to the outside and attached to, for
example, a contact plate on the back, because when you touch the negative contact
while transmitting, the range is somewhat greater.
229
Fig. 11.2a:
7-segment display Fig. 11.2b: Avago HDSP-815 7-segment display schematic
For most displays, you can get a data sheet in PDF format, which also contains the
exact wiring schematic. The pins are often laid out as in this example, the Avago
HDSP-815, which is widely available at electronics retailers. The individual segments are
usually labeled “a” to “g,” usually with the addition of an additional decimal point,
labeled “DP” (decimal point) in the figure.
230
the number of digits. For example, for 4 digits, we need 11 outputs (or 12 if we want to
control the decimal point). Also, we need fewer resistors – just one per digit. It works
like this:
The figure shows the wiring
of four 7-segment digits.
The individual segments
(“a” to “g”) are now con-
nected together for all 4
digits, and each directly
connected to an Arduino
output. The common cath-
odes of the individual digits
are now no longer con-
nected to ground, but to
another Arduino output via
a 100-ohm resistor.
Segments “a” to “g” must now be set to HIGH one after the other – one segment at a
time. Let's start with segment “a.” At first, all segments are set to LOW, so that nothing
is illuminated yet. Now the common cathode of the digit (or digits) where segment “a”
should light up is set to LOW, and the others to HIGH. Still nothing lights up, because all
segment lines are still at LOW. Now, however, segment line "a" is switched to HIGH, and
the “a” segments light up – but not all of them, only for the digits (or digits) whose
cathode has been switched to LOW.
After one millisecond, it’s the next segment’s turn. To do this, segment “a” is first
switched back to LOW so that all segments go out. Then, the common cathode of the
digits where segment “b” is to light up are set to LOW and the remaining ones to HIGH.
As soon as the segment line “b” is switched to HIGH, the “b” segments light up – but
again, only for the digits whose common cathode is at LOW.
231
We then wait for another millisecond to pass, and then comes the turn of segment “c,”
and so on. When all 7 segments are done (or 8, with the dot), about 8 milliseconds
have passed and the whole game starts again from the beginning with segment “a.” The
“frame rate” in this example would be about 125 Hz.
With a HIGH signal at one of the upper 7-segment lines (“a” to “g”) the segment is
selected, and the presence of a LOW signal at any of the four Digit lines determines on
which of these digits the corresponding segment should light up. The whole thing
changes so fast that you can see the numbers without any noticeable flickering. We
could even control the brightness by dividing the waiting time of 1 millisecond into two
shorter times, which would together add up to 1000 microseconds. This way, we could
also let each segment shine for only a part of this time.
The multiplexing method offers another advantage: It does not matter whether we use
a display with a common anode or a common cathode. In both cases, the method is the
same – with a common anode, we need only invert the control. The active segment
must then be LOW and all others HIGH, while the 4 Digit lines are activated by a HIGH
signal – thus everything is inverted.
We just have to be careful not to buy the wrong version. Some have a colon instead of
the dot or only two segments and a minus sign preceding them instead of the first digit.
The display shown here, for example, cannot drive all points, but its colon makes it
suitable for clocks. Whether it has common anodes or common cathodes for the
individual digits, on the other hand, doesn’t matter, because we can easily adjust that in
software.
232
anode or cathode per digit. For example, for a display with 4 digits, this would be a total
of 12 lines (8 for the segments including the dot, and 4 for the individual digits). Of
course, we can also wire several displays with only one digit each accordingly, but no
one wants to do that kind of work.
In the variable section at the beginning, we have the hardware information, how many
digits the display has, whether it has common anode or cathode, and, in an array, the
pins used for the segments. In the second array, we have the pins for the individual
digits. (The pins entered here are ideally suited for a 4-digit display, which I will
discuss in more detail after the sketch.) Only 4 digits are specified in the latter array,
even though it has a size of 6, so we can also connect 6-digit displays by simply adding
two more pins (again, separated by a comma). For even more digits, we’d have to
increase the size of the array accordingly.
This variable section includes the display settings. In “frequency,” we specify the
refresh rate. The frequency in which the individual segments are then clocked is 8
times higher. change_frequency specifies the frequency of the calls to a special
program section in which we can update the display later.
Then follows an array. The 20 binary numbers are the segment patterns of the
individual digits. The first bit stands for segment “a,” the last bit for “g.” From the
second-last array line we have the letters “A” to “F.” Then we have the minus and the
degree symbol. The last two (18 and 19) are blanks. The last one is also used as a
space, while we could redefine the second-last one (18).
233
The digit[] array contains the 4 digits that are to be displayed, albeit in reverse order.
Here, we can enter other values in the loop at any time. For example, 7 stands for the
number 7, and for 15, “F” is displayed, and so on. In the default setting, 1234 is
displayed. dot_pos indicates the position of the dot. With 0, no dot is displayed. 1, on
the other hand, stands for the last digit, 2 for the second-last, and so on.
void setup()
{
for (n=0; n<8; n++) // all single segments
{
pinMode(segment_pin[n], OUTPUT); // pin on output
digitalWrite(segment_pin[n], common_anode); // switch off
}
for (n=0; n<digits; n++) // all single digits
{
pinMode(digit_pin[n], OUTPUT); // pins on output
digitalWrite(digit_pin[n], !common_anode); // switch off
}
loop_time = 1000000 / (int(frequency) * 8); // µs per loop pass
loop_time >>= 3; // divide by 8 (rounded down)
loop_time <<= 3; // multiply by 8 (thus divisible by 8)
}
In setup(), first all segment pins are set to OUTPUT via a “for” loop and, depending on
the display variant, to LOW or HIGH accordingly, so that all segments are off. The
second “for” loop does the same for the pins of the individual digits.
Then, loop_time is calculated based on the desired display frequency, and divided by 8
and multiplied by 8 again. This may not seem to make any sense, but it serves to
ensure that the result is divisible by 8.
void loop()
{
while ((next_time - int(micros())) > 0); // wait for loop time
next_time += loop_time; // set next time
In loop(), the “while” loop waits for the next time unit. This way, the loop runs at a
constant clock, so that nothing flickers on the display. If the time has elapsed, the
234
comparison value goes negative, i.e. it becomes smaller than 0, so that the loop is
continued immediately – It doesn’t matter if it takes a little longer. After that, the time
for the next loop is set immediately.
Here, first the previously-active segment is turned off. Then comes the next segment,
whereby we count only to 7. Then follows segment 0. By means of a “for” loop, the
current segment is now switched for all display digits. If it is a segment of a number,
the first digitalWrite() function is executed, which is a bit complicated: We look at
digit[n] first – this variable specifies which digit is to be displayed at position “n.”
segments[] returns the segment bit pattern, and the following “&” operation assumes
the corresponding bit, which indicates whether the segment should be switched on for
this digit at this position. The comparison with common_anode inverts the switching
signal if necessary, which is then sent to the corresponding pin for digit “n.”
If, on the other hand, the current segment is the decimal point, the second
digitalWrite() function is executed. The comparison of dot_pos with n + 1 first
determines whether the dot is to be displayed at this position. Then, the comparison
with common_anode inverts the levels if necessary, before the pin for digit “n” is
driven with it.
235
In this part, the counter is used simply to check whether the cycle time for
change_frequency has been reached. The counter is then reset and we can insert our
own program sections for updating the display here. The sense behind this is that we
should not update the display too often (not more than 3 times per second).
Otherwise, should the values change too often, different digits would flicker
continuously, and they wouldn’t be clearly legible.
In the “else” block, we can now take analog measurements and sum them up for later
display. We could also measure at every pass, but in the “else” block we make sure
that not both measure and display are done in the same pass, because then we might
exceed the time unit, and the display may flicker. Otherwise, the counter is simply
incremented in the “else” block.
By the way, there are additional functions that we could append to the sketch and use
to show the contents of a variable as an integer or a floating-point number on the
display, for example. But we will come to that later.
Construction
We need:
The best choice here is to use the
Pro Mini at 3.3 V with an 8 MHz • ATmega 168 (or 328) Pro Mini board, 8 MHz
clock frequency. The ATmega168 • 7-segment display, 2 to 4 digits, 0.56-inch
is completely sufficient for most • 2 to 4 resistors, 100 Ω / ¼ W
applications. But, of course we can
also use a board with the ATmega328, just not a version running at 16 MHz. Why?
Because the segments of the display are LEDs and LEDs don’t like high blocking voltage
– according to the data sheet, some not even as high as 5 volts. The switched off
236
segments (where both digit location and segment signal are off) get the full operating
voltage as a blocking voltage at that moment. 5 volts would probably not hurt, but we
prefer to play it safe and use the 8 MHz version at 3.3 volts. If you still run the 16 MHz
version on 5 V, you should change the resistors to 220 Ω.
We only have to bend the 4 pins for the 4 digits (the common anodes or cathodes of the
each digit) over and connect them via a 100-ohm resistor. The remaining 8 pins are
soldered directly. It could hardly be simpler.
Fig. 11.2.1b: Connecting the 12 pins of a 4-digit 7-segment display to a Pro Mini board
The display is placed directly on the underside of the Pro Mini. In the photographs, you
can clearly see the directly-connected pins and the resistors on the four bent-up pins.
Here, a 0.36-inch display was used, and the two pin rows were bent apart a bit. The
0.56-inch version fits even better. At the pins of the Pro Mini that are not needed by the
display, we can attach the regular pin headers (as in the photos), before we solder the
display to it.
237
Besides the segments, “a” to “g,” we have “dp” here – still the decimal point, as a seg-
ment. The numbers 1 to 4 are the common anodes or cathodes of the individual digits,
where 1 stands for the first digit, 4 for the last. The numbers in parentheses, on the
other hand, are the physical pin numbers on the display board.
If 4 digits are not necessary, there are also corresponding 2- and 3-digit versions, which
we can also use here. Here, too, I recommend the 0.56-inch version, which fits exactly
on the pin rows of the Pro Mini. In the program, we then have to adjust the upper
variable section, accordingly:
The values refer in each case to the display being plugged into the Arduino – from
below up to the outermost pins (9 and 10) of the Pro Mini. So, for the two-digit version
with only 10 pins, Pro Mini Pins 4 and A1 are not used.
Of course, we must not confuse the top and bottom of the display. The decimal points
must be on the side with the low pin numbers of the Pro Mini. And, don’t forget: The
two, three, or four pins of the digit_pin[] array must not be connected directly, but
always via a 100-ohm resistor. Whether common_anode has to be set to “true” depends
on the display version, of course. If you set the value wrong, all segments will light up.
238
Here, we make use of the modulo operator in our calculations, for the first time. I will
explain it briefly:
The percent sign is the modulo operator. As with the slash, “/,” a division is
performed, only always on an integer. But, for the result, we now get something
completely different: the remainder.
So, in our example we get the value 4, because 25 / 7 gives 3 with a remainder of 4.
The sketch is essentially self-explanatory from the comments. Therefore, I have only
briefly summarized most of it here:
We can send integer values (i.e. whole numbers) directly to displayInt() function,
where they will be shown on the 7-segment display. Negative values are first turned
positive and marked with the “negative” variable.
239
In the first “while” loop (starting from the last digit) the individual digits are
determined, whereby a digit is reserved at the beginning for a minus sign, if necessary,
where it is entered afterward. If the number does not fit on the display, the digits are
replaced by E in the second while loop. This stands for “error” or “overflow.” If the
number was within the permissible range, then any unused digits are filled with blanks.
(You could also use leading zeroes.)
240
The displayFloat() function shows us float values (decimal fractions) on the display.
Here, we first determine how large the number is, or how many digits (including minus
sign, if necessary) are needed. If there are more than we have available, the E
characters are displayed again. If, and as long as, there are still digits available, the
number is increased tenfold and the decimal point is moved before the number is
displayed. Finally, if necessary, the minus sign is displayed at the beginning. If the
decimal point comes after the last digit, it is superfluous and is thus omitted.
241
With the displayHex() function, we can output integer values in hexadecimal. This is
done here by processing only the last 4 bits of the total, because they go from 0 to 15
and thus correspond to exactly one display digit. Then, the number is right-shifted by 4
bits, and the next display digit follows. If there are bits left at the end, i.e. if the
number does not fit on the display, all digits are filled with minus signs. An E cannot be
used here as an error message, because the E is an actual digit in hexadecimal.
void displayDegree(float value) // display values in degrees (with one decimal place)
{
digit[0] = 17; // degree sign
bool negative = false;
if (value < 0) // if value is negative
{
negative = true; // mark as negative
value = -value; // positive value
}
byte extent = 2 + negative; // needed digits
float i = 10; // comparative value
while (value >= i) // as long as further digit needed
{
extent++; // one digit more
i *= 10; // increase comparative value by 1 decimal place
}
byte pos = 1; // writing position (before degree sign)
dot_pos = 2; // point at the end (if no decimal place)
if (extent < digits) // if place for decimal digit
{
dot_pos = 3; // point before second last place
value *= 10; // one decimal place
}
242
If we append these functions to the original display sketch, we can, for example, use
displayFloat(x) to show variable “x” as a decimal number on the display. The function
then calculates the individual digits, saves them to the digit[] array, and also sets the
decimal point accordingly. But what exactly do the new functions do, and under what
conditions?
displayInt() always shows only integer numbers on the display. (Floating-point values
are rounded down.) If not all digits are needed, the leading display digits remain empty.
For negative numbers, a minus sign is displayed right before of the number. Thus, we
can show values from -999 to 9999 using a four-digit display. If the value is larger, all
digits show E. If the value is smaller, the minus sign remains in the beginning position
and the remaining digits show E. We can use 1- to 6-digit displays, but only for the int
range (-32,768 to 32,767).
243
displayFloat() displays float numbers with decimal points on the display. As many
decimal places as possible are always displayed, even with whole numbers. The
displayed number is rounded up or down commercially, i.e. depending on whether the
first digit that cannot be displayed after the decimal point is 5 or greater. For negative
numbers, a minus sign is placed directly before the numbers, so we can show values
from -999 to 9999 using a four-digit display. If the value is larger, all digits show E. If
the value is smaller, the minus sign remains at the front and the remaining digits show
Es. We can use 2- to 6-digit displays.
displayHex() outputs hexadecimal integers to the display. The digits used are 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, A, b, C, d, E, and F. The fact that mixed upper - and lowercase letters
are used is due to the limited possibilities we have with 7 segments. Negative numbers
cannot be displayed, and fractional numbers are rounded down. With a 4-digit display,
any unsigned int value can be displayed completely. With even more digits, even long
values are possible. If, on the other hand, a number is so large that it cannot be
displayed completely, minus signs are displayed in all places. We can use 1- to 8- digit
displays.
We now use the displayFloat() function and a slightly adapted form of the voltage
measurement in section 6.2.2 from page 71, in addition to our basic sketch for the
7-segment display. We can also take the resistor values for the voltage divider from the
table on page 72. Likewise, the calibration line, but with the small difference that we
now have an actual display instead of using Serial Monitor.
244
#define measure_pin A2 // measurement input (A0 and A1 used for the display)
#define gnd_resistor 10.00 // resistor to ground (in kiloohms)
#define r1_resistor 220.00 // resistor R1 (in kiloohm)
#define reference 1100 // internal reference in mV (calibrate!)
This is the modified combination of the sketch for the 7-segment display containing the
floating-point function with the sketch for voltage measurement from section 6.2.2.
Here, we have a section with constants at the beginning, in which we enter all of the
values for the voltage measurement. After that comes the variable section with the
display information. We skip the rest of the sketch, because it consists mainly of the
7-segment sketch with the functions for the floating-point display.
If we skip the other variables for the display, we come to the variables for the voltage
measurement. New here is the “clipped” variable. With this, we count if and how often
the maximum value of 1023 is measured, which can be a clue that the input is possibly
overflowed.
Again, we skip the parts of the 7-segment basic sketch and now we have two lines
from the setup(). There, the conversion factor is defined from the reference voltage
and the resistors, from which we calculate the voltage in the loop.
245
The “if” and “else” blocks at the end of the loop are still interesting. The counter is now
not only used to trigger the display, but also to count the individual measurements,
which are summed up. If the time is reached, summed-up measured value
“measurements” is calculated with conversion_factor, which must also be divided by
the number of measurements, “counter.” If, at any point, the value 1023 was
measured, the measurement range may have been exceeded. The variable “voltage” is
then assigned a number so large that the displayFloat() function indicates overflow in
any case. The value is then output, and all values for the forthcoming measurements
are reset.
The actual measurement and summation takes place in the “else” block. The “clipped”
variable counts whether (or how often) the measuring range may have been exceeded.
As in section 10.1.2 we have a voltage divider again, this time consisting of NTC and a
resistor. On page 194 you'll find all the information you need.
246
First, A3 is defined as the measurement input. Then follows a variable section with all
data regarding the NTC and the associated resistor. Then comes the variable section
with the data regarding the display, which should be a 4-digit display in this case. The
next section contains the display settings.
247
The display frequency is also set to 200 Hz here. The frequency at which new values
are displayed was reduced to 0.5 Hz, i.e. a new value only every 2 seconds, because
temperatures usually change slowly. It may happen that the time interval is exceeded
slightly every 2 seconds when a new value is calculated, but this not a problem. You
won’t see any flickering.
We’ll skip most of the rest of the sketch for now, because it consists essentially only of
our basic sketch for the 7-segment display and the output functions. Only the “if-else”
blocks at the end of the loop are interesting.
248
Thermostat
Here are a few suggestions on how you can easily expand the thermometer to a
thermostat. A thermostat has the task of regulating the temperature, and to turn on the
heat if the temperature falls below a certain value, or to turn on the cooling if the
temperature exceeds a certain value. This is how, for example, a refrigerator regulates
its temperature.
We can easily define such a threshold value with another variable. Whenever a new
temperature value is displayed, we can compare the temperature with the threshold
value using a few extra lines in our sketch and switch an additional output on or off,
accordingly. How to switch other devices with this output is described in detail in
sections 7.2 and 7.3.
With two buttons (for up and down) we could also make the threshold adjustable,
preferably so that the new threshold is sent to the display after each change. This way
we can read exactly what we’re. If the buttons have not been pressed for a while, the
current measured temperature will be displayed instead of the set threshold value. The
display frequency, change_frequency, should be set low – preferably below 1 Hz, so that
the measured temperature is not displayed too soon after setting.
249
Excellent alternatives are text LCDs such as these with 2 or 4 lines and 16 or 20
characters per line each, because these have the necessary character set already
built-in, and we can drive them directly by specifying the corresponding ASCII
characters, instead of setting and resetting individual pixels. In other words, these
displays know what the individual characters look like. In addition, we can also define a
few characters of our own that the character set doesn’t contain.
250
These displays are wired as shown in the previous picture on the left. The upper two
pins (15 and 16) are for the back-light. DB0 to DB7 are the data bits. In principle, this
is a parallel interface, but since we always send half bytes, we only need 4 data bits. “E”
is the enable pin for activation. R/W stays grounded to GND, because we only want to
write data, not read it. RS is the register bit. The display needs an analog voltage at V0
for contrast adjustment. Then, there’s the operating voltage, VDD, and ground, here
called VSS, that must be connected to GND.
For the back-light, the circuit has a series resistor of 100 Ω. All these displays can be
operated on 5 volts, but, for the back-light, a lower voltage (e.g. 3 volts) is
recommended. The series resister should be suitable for all versions. If less light is
desired, you can also increase the series resistor to 220 Ω.
Another change is that the 100 Ω resistor for the back-light is no longer connected to
VCC, but also to an Arduino output. That way, we can turn the back-light on and off. If
you want, you can also use a PWM-capable output for this, experiment with the PWM,
and thus even control the brightness (as well as the contrast).
LiquidCrystal.h
This is the library for controlling such displays. If we include this, we can easily write to
the display without worrying about the activity on the individual lines. We can even
choose which 6 digital outputs of the Arduino we want to use for this. Usually, it’s Pins
2, 3, 4, 5, 11, and 12, but the other two output pins in the second schematic, for V0
and the back-light, aren’t controlled by the library. They are just as freely selectable,
but V0 needs a PWM-capable output if no potentiometer is used.
If such a text display is wired directly to the Arduino, it is best to use the
LiquidCrystal.h library. This offers many functions, but it would be beyond the scope
of this book to explain them all here, especially since we don’t need them all. Instead,
below is a small example program that explains the essential functions, and even
defines its own character - the often-useful Ω character, which is not included in the
default character set. If you want to know more, all of the functions are explained at
arduino.cc/reference/en/libraries/liquidcrystal
252
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // display pins (RS, EN, DB4, DB5, DB6, DB7)
First we have to include LiquidCrystal.h to be able to use the display functions. The
second line defines the Arduino’s digital pins that we’ll use.
Here we do something special right away. We define the ohm character, which does not
otherwise exist, in the small ohm[] array. Each binary number represents a bit-row.
void setup()
{
lcd.createChar(0, ohm); // define ohm character as character no. 0
lcd.begin(16, 2); // display with 2 lines and 16 characters per line
lcd.print("Hello!!!"); // display text in first line
lcd.setCursor(0,1); // set text position at character 0 in line 1 (beginning of 2nd line)
lcd.print("Ohm character: "); // display text
lcd.write((byte)0); // display the character (Ω) defined as no. 0
}
In setup(), we save the ohm[] array directly to the display’s memory as character 0.
Then, we have to specify how many characters per line and how many lines our display
has. Then we write “Hello!!!” to the display. Because the display’s cursor was still in its
initial position (Line 0, Character 0) the text appears right at the beginning of the first
line. Then, we set the writing position to the beginning of the second line (Line 1,
Character 0), then write “Ohm character: ” and output our self-defined ohm symbol
thereafter.
void loop()
{
}
Unusually, there is nothing in loop() here, because our example program should only
output a one-time message after switching on.
253
11.4 Mini lasers If you consider how expensive the first laser
pointers were at the end of the last century,
it’s incredible how cheap such mini lasers (like
those built-in to laser pointers) are on offer
today. You can get them from China in a pack
of ten for 20 cents each (plus shipping costs).
254
For example, we could build a high-quality light barrier, clocking the laser at a frequency
of exactly 38 kHz. The infrared receiver from section 10.2.1 on page 199, which is
sensitive to exactly this frequency, then serves as receiver (even over larger distances).
The laser diode does not make infrared light, but red is close to infrared, so a direct red
laser beam is quite sufficient for controlling the infrared receiver. We could even reduce
the laser power with an additional series resistor. However, a certain minimum current
(threshold current) is always necessary for a laser diode to function properly at all.
In the same way, we could transmit serial data with such a setup, just like an infrared
remote control does. The single data pulses would have to be in the order of 1 ms (or
longer) and clocked at 38 kHz, as described in section 10.2.1. If the laser beam is
precisely aligned, the infrared receiver could work on the decoded pulses over a greater
distance when the line-of-sight is clear.
Even more interesting are the type of laser effects that can be achieved with such mini
lasers. For example, attach a small mirror (e.g. round with a 1 cm diameter) to the
center of the impeller of a small fan (e.g. 4×4 cm, 5 V), and glue it with hot glue in
such a way that it is not exactly flat, but slightly slanted—this can be achieved by by
pressing it a little harder on one side—a laser beam directed at it will be reflected as a
circular line (or, depending on the angle of incidence, an ellipse) when the fan is
running. Now, if you point many lasers (because these parts cost almost nothing) at the
mirror, many circles will be reflected, and you can project these onto a wall. The
distance of the individual circles results from the distance of the lasers. The size of the
circles results from the slant of the mirror. The size of the whole projection results, of
course, from the distance to the wall where the lasers are projected.
If we then let the lasers blink individually or in groups, we get an interesting effect with
blinking circles. To control the lasers, we could use the chaser program on page 94. By
switching the lasers on and off very quickly (in the range of 1 kHz) even special effects
(such as dashed circles) are possible. But, for this, you would have to synchronize the
frequency with the speed of the fan, which can prove very difficult. The fan should make
about a hundred revolutions per second in order for the circles not to flicker. A general
problem is also the exact alignment of all of the lasers to the small mirror.
255
You can see the common version for MicroSD cards here. There’s also a version for the
larger “normal” SD cards that were common in the past. When connected to the
Arduino, we can use the module to write data to and/or read data from the card. There
are a few limitations, though:
Limitations
The usual library, SD.h, only supports drive volumes and files in an older format. The
card used must be formatted as FAT32 (or FAT16). With common Windows operating
systems you can only format media (in this case, SD cards) up to maximum of 32 GB
under FAT32. It is therefore recommended not to use cards larger than this. Another
limitation is the old format for file names, which have a maximum of 8 characters (plus
a dot and three characters for the file extension). Furthermore, files in FAT32 format
can also only be up to 4 GB in size, but this is quite a lot. We can, therefore, live with
these constraints. We just have to know about them and observe them.
256
To access SD cards, we usually make use of the SD.h library, which provides many
functions for the purpose – too many to list them all in detail here. For writing and
reading files, we only need a few functions anyway, which I explain in the following
sketch. If you want to know more about this library, you can find descriptions of all of
the functions on the web at arduino.cc/reference/en/libraries/sd, along with a few
more example sketches.
To access SD cards, we have to include two libraries here, one is SPI.h, to use the
interface, and the other is SD.h, for the SD card functions. Then, the chipSelect
constant defines the input that’s connected to the CS pin on the SD module. File
declares a file object that will be used later. The name, testfile, is something like a
variable name – not to be confused with the actual file name, which can be completely
different.
void setup()
{
Serial.begin(38400); // serial transmission to PC
In setup(), serial transmission to the PC is first started. Then, using SD.begin(), the
card is initialized, and, if initialization is successful, a message is sent to the PC.
while(true); is an endless loop that halts the program in the case of error.
257
Using SD.open(), a file with the name test.txt is opened, specifically for writing, as the
specification FILE_WRITE indicates. If the file does not exist yet, it is created. The file
pointer position is set to the file’s end. For a newly-created (empty) file, this is also the
beginning of the file. For existing files, it means that writing continues at the end of the
file, so that forthcoming data will be appended to the end.
If opening the file was successful, different data are now written in. You have to
distinguish between “printing” and “writing.” As we already know from Serial.print()
and Serial.println(), the data is sent out as plain-text. A number, for example 187,
does not arrive as a single byte with the value 187, but as three ASCII values—one for
each digit: 1, 8, and 7, respectively. If you want to send out the actual bytes directly,
you use the corresponding .write() function.
Afterwards, the file is closed again and a message is output via serial port. If, however,
opening the file did not work, the program is halted in the “else” block by means of an
endless loop, after a corresponding message has been sent to the PC.
For all file actions (opening, writing to, and closing the file) the “testfile” object that we
defined with “File” is used. This way, the actions can be applied to the specific file.
Nonetheless, only one file can be open at a time.
258
Reading from the file is done in a similar manner. Here, the FILE_WRITE specification is
omitted when opening the file, and the file is thus opened for reading.
With .available(), we find out if there is more data to read. .read() then reads one byte
at a time and sends it to Serial Monitor using Serial.write(). Here, we actually need
Serial.write(), instead of .print(), because we already have the ASCII codes of the
characters to be displayed.
In Serial Monitor we now also recognize what the many numbers that we wrote into
the file earlier mean, using .write(). These numbers were the individual ASCII codes
for the character sequence, “Second Line!”
The empty loop() does nothing here. This example program should merely write and
read after power-on. There is nothing else for it to do thereafter.
This simple program is only an example. It saves a short text file and calls it again to
output its contents to the serial monitor. If the file already exists, the new text is
appended to the file, so the file gets a little longer with each call.
259
260
However, if we follow a few rules, it’s also possible to solder the leads if necessary.
First, the soldering iron should be set a little hotter than usual. Yes, I really mean
hotter, because otherwise the iron takes much too long until the contact surfaces
reach the solder’s melting temperature. A soldering iron that’s not hot enough would
heat up the whole battery significantly until the latter finally accepts the solder.
Now, we introduce the soldering tip at a flat angle to the battery connection so that it
touches the battery over as large an area as possible and thus gives off as much heat
as possible. At the same time, we apply fresh solder to the soldering joint (exactly at
the border between the soldering tip and the battery contact). When the solder melts
onto the battery, we remove the soldering iron immediately. This entire process
should not take longer than three seconds. If not, the soldering iron may not be hot
enough yet.
Now, we’ve applied a blob of solder to the battery contact, but the wire’s not yet
attached. We strip the wire and tin the end with a lot of solder. Only when the battery
has cooled down do we solder the wire in a second step. To do this, we first hold the
wire a few millimeters above the battery contact. With the soldering iron, we liquefy
the solder on the wire and then press both (soldering tip and tinned wire) together
onto the battery contact. The solder melts very fast and the already-existing solder
blob forms a clean solder joint with the wire. Then we remove the soldering iron
immediately.
Most batteries can be soldered very well this way. Occasionally, however, we come
across alloys that don’t like solder. It may help to sand or otherwise roughen the
contact surfaces beforehand. Soldering on the positive pole of the battery is
usually a bit easier, since its contact is smaller. Nevertheless, we have to be very
careful not to touch the insulated outer edge and melt the battery’s plastic,
because underneath the plastic is the battery’s metal casing—the negative pole—and
a very nasty short circuit could occur.
261
A lower final charging voltage, on the other hand, is not critical – we can set our
charger to a final voltage of 4.1 volts, for example. This even protects the battery, but it
also won’t be completely full, so it will have somewhat diminished capacity.
We also have to be careful when discharging, because the cell voltage should not drop
below 2.8 volts. It makes sense to switch off at 3.5 or 3.4 volts, because, from then on,
the voltage drops faster and faster (and there’s hardly any energy left in them anyway),
and this also spares the battery. It’s best to recharge empty batteries timeously (at
least a little bit).
All in all, the most gentle way to use a lithium-ion battery is to charge it only up to 2/3
capacity and discharge it down to 1/3. If we want to store it for a long time, it’s best to
charge it to about 3.9 volts and then store it in a cool place.
With lithium polymer batteries, there’s usually a very narrow circuit board (not much
thicker than a match) on the connection side. With 18650 round cells, it’s usually a
small, round circuit board at the negative terminal. The actual cell is then e.g. one
millimeter shorter in order to get back to the correct size with the protection circuit
included. From the outside, you usually can’t tell if such a protection circuit is built in.
Several permanently-installed 18650 cells (e.g. in a laptop or single cells intended for
such purposes) usually do not have their own protection circuit, since a common charge
controller with protection circuit is used there anyway. With lithium polymer batteries,
the protection circuit can often be seen when looking closely.
For battery cells without a protection circuit, one should definitely be retrofitted to pre -
vent excessive discharging or charging (with a defective charger). You can get them
262
Fig. 12.3: 2S, top and underside – 3S protection circuit – two 4S protection circuits
On the 2S protection circuit on the left, in addition to B+ and B-, there is also BM for
the connection point between the two cells. With the 3S, 4S, etc., there are several of
these connection points, numbered consecutively, on the protection circuit board. An
exact connection diagram can usually be found along with the respective product.
12.4 Balancers
Unfortunately, it is not enough, in the long run, to protect the cells only from too high a
charging voltage and discharging too deeply, because, after charging and discharging
many times, battery cells in a series connection can no longer be assumed to be equally
full. However, the charging process gets stopped as soon as the first cell is full,
otherwise that cell would get charged to too high a voltage, but the remaining cells still
aren’t yet completely full.
263
Likewise, when discharging, the circuit must be switched off immediately when the first
cell is completely empty, even if the remaining cells could still hold out for quite a while.
Thus, the entire battery of cells can no longer supply the full capacity. In this case, all of
the cells are OK, but are just charged to different levels.
The board on the right has an additional protection circuit, which switches off the
battery if the cell voltage drops too far. The output pins (OUT) are then disconnected
from the battery pins (B), or disconnected at the negative terminal, because OUT+ and
B+ are directly connected to each other, as you can see if you take a close look.
Each of these charge controllers have two LEDs, which indicate whether the battery is
full during charging. In addition, the boards each have two terminals next to their USB
sockets, where you can tap the 5-volt USB voltage during charging. You could even
solder a 5-volt power supply to these connectors. Then, you won’t need the Micro USB
socket at all, or you could even use it to power other devices!
264
Wouldn’t it be great if we could simply do without these components? But, that’s not
possible, because we can only measure voltages up to 1.1 volts directly with the
internal reference, and to take VCC as a reference makes even less sense if it’s VCC
that we want to measure – we’d always get 100% (1023) as the measured value.
But, with a clever trick, it works. We can actually measure the operating voltage without
connecting a single component to the Arduino.
265
Using analogRead() directly, we can only measure the analog inputs, unfortunately.
Internal measurement of the reference voltage is possible, but this is not supported by
the Arduino IDE, so we also have to use a workaround by accessing the
microcontroller’s registers directly: What the analogRead() function usually always does
for us quite easily, we now have to program ourselves, but that’s fine.
void setup()
{
Serial.begin(38400); // prepare serial transmission
}
First, the internal reference voltage is set as a constant. If we need an exact result, we
can calibrate this later. Variables are not defined here yet, because we want to insert
the measuring function into other programs as well, so required variables are defined
only later (only locally, within the respective functions). In setup(), serial transmission
is prepared in order to output the measurement results. There is nothing else to do.
void loop()
{
Serial.print("Battery voltage (VCC) = ");
Serial.print(batVoltage()); // return value of the batVoltage function
Serial.println(" V");
delay(1000); // wait 1 second
}
The loop() function outputs the operating voltage every second. For this, we now have
a very suitable function called batVoltage(), which directly gives us the battery voltage
(VCC) as a float value.
266
Now we have the function in question, which determines and returns the battery value.
Here, we access the microcontroller registers directly for measuring. The comments
explain roughly what happens at each step. If you want to understand it exactly, you
should have a look at the registers in the ATmega328’s datasheet. Here, we need to
know this: Besides access to the reference voltage as measurement input, a special
kind of measurement (Free-Running mode) is used here, where measurements are
performed continuously, without end. A bit set in the ADCSRA register indicates when a
new measurement value is available. Afterwards, you have to clear this bit by setting it
again (no, not resetting it). Yes, I guess the chip developer must have had a laugh
with this.
The “while” loop within the “for” loop waits for the next measured value, and the single
values are summed up so that 64 measurements result in a measured value with a
16-bit resolution. This is also useful, because the first measurements (directly after
setting the reference as input for the measurement) may not be that accurate yet.
At the very end, the registers are reset again, and the measurement range (i.e.
battery voltage VCC) is determined based on the measured reference voltage. This
voltage is then returned as a float value.
267
Calibration
Since the reference voltage can deviate by ±10%, the measured battery voltage
naturally also has an error tolerance of ±10%. If we switch off at 3.15 volts, for
example, then the actual cutoff is between 2.84 and 3.46 volts. This accuracy would be
sufficient to protect the battery from too deep a discharge if necessary, especially since
the actual deviation is usually much smaller.
If you still want to calibrate the measurement, you can do it quite easily. We just have
to measure the battery voltage with an accurate multimeter and compare it with the
measured value shown in the Arduino IDE’s Serial Monitor. Then, we can calculate:
The power-saving and standby settings are also not directly supported by the Arduino
IDE, but there are libraries that can be included quite easily. With the line
#include <avr/sleep.h> we add such a library, which offers additional functions to put
the Arduino to sleep. Besides sleep.h, power.h is also often included. The latter library
offers additional functions to switch off some of the microcontroller’s individual
components, but on closer examination it is much easier to do without this library and
write directly into the microcontroller’s corresponding register.
268
#include <avr/sleep.h>
void setup()
{
}
void loop()
{
standby(); // in real application, of course, call only when needed
}
Here we include the sleep.h file right at the beginning. There is nothing to do in
setup(), and in loop(), we only call the standby() function, which of course makes no
sense, because this program does nothing but sleep, but this is just to show how it’s
done.
void standby()
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // preset to deep sleep
sleep_enable(); // activate sleep mode
PRR |= 64+32+8+4+2; // disable timer, SPI and USART
sleep_mode(); // trigger sleep mode
}
This is the standby() function that puts the Arduino to sleep. set_sleep_mode() is one
of the functions provided to us by sleep.h. With this, we set the default sleep mode, in
this case Power-down, the deepest sleep mode. (There is also Standby mode, which
uses a bit more power, but don’t confuse it with our standby() function, so-named for
simplicity’s sake.) Next, we enable Sleep mode so that it can be triggered. Then, we
write to the PRR register to turn off unnecessary components. (You could also do this
with a power.h inclusion, but again, it’s much easier this way.) Finally, sleep mode is
triggered, and remains until we revive the Arduino with a reset.
A pin to wake up
Now, sometimes it can be useful if there is a way other than a full reset to wake the
Arduino up again, so that it can directly continue with the program its running. This can
be done with a few small extensions. The Arduino can then be woken up at any time
with a digital signal at one of its inputs (e.g. by pressing a button). It then continues its
work as if nothing had happened.
269
#include <avr/sleep.h>
byte PRR_reg;
void setup()
{
pinMode(wake_pin, INPUT_PULLUP); // wake pin as input with pull-up
}
void loop()
{
standby(); // in real application, of course, call only when needed
}
void standby()
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // preset to deep sleep
// Triggers wakeUp function on falling edge at wake_pin:
attachInterrupt(digitalPinToInterrupt(wake_pin), wakeUp, LOW);
PRR_reg = PRR; // save actual state
sleep_enable(); // activate sleep mode
PRR |= 64+32+8+4+2; // disable timer, SPI and USART
sleep_mode(); // trigger sleep mode
}
void wakeUp()
{
detachInterrupt(digitalPinToInterrupt(wake_pin));
PRR = PRR_reg; // re-enable components
}
Most of the stuff here is the same as in the previous version of the sketch. There are
only a few additional lines, so I’ll only explain the additions. Right at the beginning,
wake_pin is defined. This must be only Pin 2 or 3, because the Arduino can only be
woken up by one of these two. Then, we have the PRR_reg variable.
270
In setup(), the wake_pin is now defined as an input with an internal pull-up resistor. In
our standby() function, an interrupt is now also prepared using attachInterrupt(),
which triggers the wakeUp() function as soon as the wake_pin changes to LOW.
Furthermore, the original content of the PRR register is written into the PRR_reg
variable so that we can to reset it later on.
The wakeUp() function is also new – it is executed first upon waking up. There, the
previously-set interrupt (which was now executed) is deactivated again. Then, the
variable is written back into the PRR register to re-enable the corresponding
components. From the wakeUp() function, the processor then jumps back into loop()
and continues the program flow.
Now we can wake up the Arduino at any time with a push button
that switches digital input Pin 2 to ground. Although we have
activated the internal pull-up resistor, we should use an additional
external pull-up just to be safe, as in in the picture on the left,
because we cannot rely on the internal resistor in Sleep mode,
unfortunately.
271
#include <avr/sleep.h>
void setup()
{
Serial.begin(38400); // prepare serial transmission
}
void loop()
{
Serial.print("Battery voltage (VCC) = ");
Serial.print(batVoltage()); // return value of the batVoltage function
Serial.println(" V");
if (batVoltage() <= shutdown_voltage) // if end voltage (empty) is reached
{
standby(); // shut down
}
delay(1000); // wait 1 second
}
Here, first the battery voltage value is output, which is done very adequately using our
batVoltage() function. Then, the function is called again and compared with
shutdown_voltage. If this bottom voltage threshold is reached, the chip is switched off
using the standby() function. Then, there’s another 1-second delay.
Thus, the program outputs the battery voltage every second until the battery is low,
and then switches off.
In loop(), the batVoltage() function is called twice, because we need this value twice.
We could, however, define a variable for the battery voltage and assign the result of
the batVoltage() function to it at the beginning of loop() – this way, you would only
have to measure once per loop and could then access the variable several times during
the rest of loop().
Of course, if you have your own application, you have your own loop() content. The
only thing necessary for the battery protection is the “if” comparison with the
standby() function call, which needs to be executed only occasionally during the
running of the program.
272
void standby()
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // preset to deep sleep
sleep_enable(); // activate sleep mode
PRR |= 64+32+8+4+2; // disable timer, SPI and USART
sleep_mode(); // trigger sleep mode
}
Finally the functions batVoltage() and standby(), which we already know from the
previous chapters. The standby() function is the simple version in which the device can
only be woken up with a reset.
273
There are also Pro Mini boards with an 8 MHz clock frequency. These are often even
cheaper and also recommended if there’s not enough space for a LilyPad. But, unlike
the LilyPad, the Pro Mini board has a voltage regulator for VCC plus a power LED, which
is permanently on. For devices with very low power consumption that are supposed to
run on a battery for a long time—possibly even weeks or months in Standby mode—
these components are rather bothersome, because the LED consumes power all the
time, and even though we don’t use the voltage regulator at all because the battery is
connected directly to VCC, this LED is always also connected to VCC and draws a
nominal current.
Alternatively, you can remove excess solder and short circuits using desoldering wick.
This is finely-braided copper strand with integrated flux, which thus eagerly absorbs
solder. We place this stranded braid on the spot with the solder residue to be removed,
and then press flat on it (i.e. over a large area) with the soldering tip.
274
Fig. 13.4: Voltage regulator and power LED (or series resistor) on Pro Mini boards
The picture shows a few common Pro Mini boards, with orange dots to indicate which
components should possibly be removed for battery operation. The somewhat larger
component (with at least three connections) is the voltage regulator. The very small
component is the power LED or the corresponding series resistor, depending on the
board layout. (In a series connection, it’s sufficient to remove either the LED or the
series resistor to break the circuit.)
For our construction, we can use a Pro Mini board at 8 MHz (modified according to
section 13.4) or an 8 MHz LilyPad.
I recommend 5 mm (or 3 mm) LEDs in red or green. If you use blue or white LEDs, you
might want to reduce the series resistors, e.g. to 120 Ω. If you want it dimmer, use
higher values.
275
Depending on the
construction (e.g. in
a housing with holes
for the LEDs), you
can free-wire them
with the respective
series resistor, as
well as the push-but-
ton (depending on
the installation posi-
tion). A circuit board
is therefore not pro-
Fig. 13.5a: Schematic for electronic die
vided.
For a USB charger (charge controller), we can use both types from section 12.5 on
page 264. If we use the bigger one, the battery is doubly-protected against excessive
discharge, because the sketch also contains a low-battery shutdown. Only the battery is
connected to the two “B” terminals of the larger charge controller – the rest (Arduino
and pull-up resistor) are connected to the OUT terminals.
276
Now, for the first time, a sketch that also uses the ATmega chip’s internal EEPROM. For
this, first a brief explanation:
Here, we include EEPROM.h right at the beginning, in order to use the functions
As an example, we write the value 57 into the memory location No. 392. Each
memory location contains an individual byte (storing values of between 0 and 255).
The ATmega168 has 512 EEPROM memory locations (0 to 511), and the 328 has
1024 (0 to 1023).
We could just as well use EEPROM.write() to write a byte into the EEPROM, but with
EEPROM.update(), the value is only written if it is not already there, i.e. if it has
changed since the last write. This preserves the memory, because it can only be
written to 100,000 times according to official specification, even if, in practice, the
EEPROM usually proves to be much more robust.
Here, the memory location is read again and the content is assigned to variable x.
There are other EEPROM functions, e.g. for larger data types, but we don’t need
them. Instead, we can also split larger types and store them as individual bytes in the
EEPROM, and, likewise we can then read them out again later and put them together.
277
#include <EEPROM.h>
#include <avr/sleep.h>
First, the required libraries are included. The reference voltage and the pins used are
defined as constants. It’s important not to mix up the LEDs. For example, led_1 must
be the LED at the top-left. But, it does not have to be on Pin 3. All digital outputs can
be used in any order. Only Pin 2 is reserved for the push-button.
Next comes a block of variables that define the die’s behavior. Best to set switch -off
voltage off_voltage to 3.15 volts. This way, a calibration is not necessary. “active” must
be set to HIGH. LOW would be correct if the LEDs were not connected to ground but to
the positive terminal (VCC). “changes” determines how often the display changes
rapidly before the final die value is shown. first_time and last_time define how rapid
this change is. The first change takes only 80 ms, the final one half a second. Finally,
display_time specifies how long it takes until the die turns off – here, 60 seconds. This
period should not be too short, in case a game move is delayed, so you can still see
what you rolled.
278
Then, the remaining variables that are needed. There are no further settings.
void setup()
{
pinMode(wake_pin, INPUT_PULLUP);
pinMode(led_2, OUTPUT);
digitalWrite(led_2, !active);
pinMode(led_3, OUTPUT);
digitalWrite(led_3, !active);
pinMode(led_4, OUTPUT);
digitalWrite(led_4, !active);
pinMode(led_5, OUTPUT);
digitalWrite(led_5, !active);
pinMode(led_6, OUTPUT);
digitalWrite(led_6, !active);
pinMode(led_7, OUTPUT);
digitalWrite(led_7, !active);
In setup(), first the inputs and outputs are set and all LEDs are switched off.
Then, still in setup(), 4 bytes are read from the EEPROM and concatenated into the
long variable, random_pos. For this, we use EEPROM memory locations 121 to 124,
although we could just as arbitrarily use others. The random_pos variable serves as a
position in the random generator, because the random numbers are not really random,
but are determined by a fixed algorithm, which means that after each restart, we’d roll
the same sequence of numbers. Instead, a new position (of the random generator) is
saved to the EEPROM each time. This way, you get different numbers each time, even
after a reset. Then, the position is read out and the random generator is set using
randomSeed().
279
void loop()
{
pressed = false;
if (batVoltage() > off_voltage)
{
for (n = 0; n <= changes; n++) // dice roll (multiple changing display)
{
if (!digitalRead(wake_pin)) // if button is still pressed
{
n = 0; // maintain quick change
}
do
{
w_value = random(1, 7); // random number 1 - 6
}
// repeat if same, previous or opposing number
while (w_value == before || w_value == before_before || w_value == 7-before);
In loop(), “pressed” is first set to false. (The variable is true later, if the die is rolled
again.) Then, we ensure that the battery voltage is in the green range, because only
then is the die rolled and the value displayed. In the “for” loop “n” counts the displayed
numbers until the display stops with the final number. Then, the wake_pin is read. If
the button is still depressed, “n” is reset, so the rapidly changing display keeps going
until you release the button.
In the do-while loop (inside the “for” loop) a random number (between 1 to 6) is now
generated as often as necessary until it meets three conditions: It must be different
from the number currently displayed. It must also not be the previously displayed
number, and not the opposite die side to the currently displayed number. The purpose
of this is that during rapid change (as with a real rolling die) there will always a
neighboring face to the current one displayed, but not the previous one. In the sketch,
the condition is formulated in the opposite way, because it is not the condition which
must be fulfilled, but the condition under which another number is chosen.
280
else if (w_value == 2)
{
digitalWrite(led_1, active);
digitalWrite(led_2, !active);
digitalWrite(led_3, !active);
digitalWrite(led_4, !active);
digitalWrite(led_5, !active);
digitalWrite(led_6, active);
digitalWrite(led_7, !active);
}
else if (w_value == 3)
{
digitalWrite(led_1, !active);
digitalWrite(led_2, !active);
digitalWrite(led_3, active);
digitalWrite(led_4, active);
digitalWrite(led_5, !active);
digitalWrite(led_6, !active);
digitalWrite(led_7, active);
}
else if (w_value == 4)
{
digitalWrite(led_1, active);
digitalWrite(led_2, !active);
digitalWrite(led_3, active);
digitalWrite(led_4, active);
digitalWrite(led_5, !active);
digitalWrite(led_6, active);
digitalWrite(led_7, !active);
}
else if (w_value == 5)
{
digitalWrite(led_1, active);
digitalWrite(led_2, !active);
digitalWrite(led_3, active);
digitalWrite(led_4, active);
digitalWrite(led_5, !active);
digitalWrite(led_6, active);
digitalWrite(led_7, active);
}
else if (w_value == 6)
{
digitalWrite(led_1, active);
digitalWrite(led_2, active);
digitalWrite(led_3, active);
digitalWrite(led_4, active);
digitalWrite(led_5, active);
digitalWrite(led_6, active);
digitalWrite(led_7, !active);
281
The number that has just been selected is now displayed here, even while the die is
being rolled. It does not have to be the final number yet. The last and penultimate
displayed value (“before” and “before_before”) are updated first. Then, depending on
the current number, the corresponding LEDs are switched on or off.
Now the current display time “real_time” is calculated, which lies somewhere between
first_time and last_time, depending on “n.” The formula is relatively complicated
because here we do our calculation using reciprocal values and then get again the
reciprocal value of that as the end result. We thus change not the time, but the
frequency continuously. This gives a smoother course. The determined time is now
passed to delay(). Then comes the block’s closing brace, after which we have the “for”
loop left again.
Now to check whether a new position for the random generator has already been
stored in the EEPROM. If not, the corresponding variable is defined, based on the old
position and the system time, and stored (broken up into four bytes) in the EEPROM.
The memory locations, 121 to 124, were chosen completely arbitrarily. They only have
to be the same as the ones that we will read the next time the program starts at
setup().
By the way, this part of the program has to be executed only once. If you roll the die
again or wake the Arduino up using Pin 2, the program continues with the next random
numbers, even if the die was in standby mode for months. Only at restart (battery
change or Reset button) is this position lost. But, in that case, new_random_pos is also
0 again, and that counts as false so it’s read from the EEPROM and updated there.
282
Now that the final number is already displayed, we have to wait for display_time until
the LEDs are switched off. Normally, this is done with a simple delay. But that doesn’t
work here, because we have to check the button to see if the die will be rolled again.
Therefore, we count off the remaining time in a “while” loop, which is exited when the
button is pressed. Instead of a total delay, we create many single delays of 1 ms each
and query the digital input afterwards each time. The exclamation mark (NOT sign)
with which we invert the signal for the “pressed” variable is important, so that a LOW
signal counts as “true”, and HIGH counts as “false”.
This “else” block belongs to the check (at the very beginning) whether the battery has
enough voltage. It is superfluous, and therefore only filled with comments. If you
want, you can add an alternative action here. This should be short and use only
minimal current, because the battery is already low when this part is executed.
283
Here, all LEDs are switched off first. This is important because the outputs remain
switched even in sleep mode. If the button has not been pressed, the Arduino is sent
into deep sleep, which we’re already familiar with from section 13.2. If the button was
pressed, Sleep mode will be skipped and the loop will start rolling the die again.
Left now is just the wakeUp() function, which we’ve met in section 13.2, and
batVoltage(), from section 13.1.
284
digitalWrite(13, !active); // switch off LED 13 (only if pin 13 is used for an LED)
Rolling the die is again done with the Reset button or by switching Pin 2 to ground
briefly.
However, the LED effect board is not a real substitute, because we don’t have the LED
arranged in a way that corresponds with dots on a die. While we can freely choose
the LEDs used in the sketch, we don’t have an LED in the center, for example. Also, a
standby operation lasting for days is not recommended here, because inevitably
either LED 13 or other on-board LED will light up. A solution can be to set Pin 13 as
an input, so that both LEDs are (mostly) off.
The ingenious inventor, Heron of Alexandria, already built such cheats into his devices
2000 years ago, but we’re of course decent and wouldn’t so such things, so we should
leave it here at fantasizing. If you still want to cheat, you have to work out a concrete
solution yourself. The book finally tells you how to do it.
285
We can also speed up the measurement itself. It still needs a few microseconds, but we
don’t have to wait for the result, and can do other tasks with the program in the
meantime.
void setup()
{
Serial.begin(38400); // prepare serial transmission
In setup() and loop(), we only have a short example, which shows how to use the
following functions. Using analogSelect() and freeRunning(), we call two of these self-
defined functions. analogSelect() requires 2 parameters, first the input to be
measured, and then (separated by a comma) the reference voltage that defines the
measurement range. As with analogReference(), we can choose between DEFAULT
(VCC) and INTERNAL (1.1 V).
It makes sense to include these two function calls in setup(). This way, the continuous
measurement starts and in the loop() function we can directly access the results.
286
void loop()
{
Serial.println(getValue()); // get new value and display it
delay(1000); // wait 1 second
// The setup and loop content is just an example.
// For your own use the following functions are required.
}
Our new function, getValue() returns the measured value, as before with
analogRead(). If there is no new (unread) value yet, the function awaits the value. If a
value is already available, it is returned immediately and output serially. Of course, the
delay line makes no sense if you’re trying to save time. In this example, it serves only
to preset the clock for the serial output.
Now we come to the first new function, analogSelect(), which is passed two
parameters (the input pin and the reference), both passed as byte variables. Yes –
behind all the constants, such as A0, A1, INTERNAL, DEFAULT, etc., are really only
simple numbers. For example, A0 is 14 (comes after Pin 13), A1 means 15, INTERNAL
stands for 3 and DEFAULT is just another word for 1, at least on the ATmega328 and
168. If you select another board with another microcontroller in the Arduino IDE,
completely different numbers may be assigned.
Our value for the analog reference is now trimmed to two bits, because the number
must not be greater than 3, and there are only two bits for it in the register. If the
selected input is really an analog pin, it is set to an input (just to be safe).
287
Then, 14 is subtracted from the value, so A0 becomes 0 again because we need it for
the register. Then, the pin value is truncated to 4 bits and then written into the ADMUX
register together with the value for the reference – the channel (pin value) into the last
4 bits (0 to 3) and the reference specification into bits 6 and 7.
The valueAvailable() function can be used to check whether a new (unread) measured
value is already available. If so, it returns true, otherwise false. To do this, the function
simply checks whether the ADIF flag is set.
int readValue() // reads the last measured value (even if it has already been read)
{
int adc_value = ADC; // take measured value
ADCSRA |= (1 << ADIF); // set ADIF bit to clear it
return adc_value; // return measured value
}
288
The readValue() function always returns the measured value immediately. For this
purpose, the last measured value is read out and the ADIF flag is reset, because this
signals whether a new (unread) measured value is present. With this bit, we have the
peculiarity that it must be set to 1, so that it drops back to 0. The function therefore
always returns the last measured value, even if it has already been read.
int getValue() // get new measured value (waits before if none is available yet)
{
while (!(ADCSRA & (1 << ADIF))); // wait if necessary for new measured value
int adc_value = ADC; // take measured value
ADCSRA |= (1 << ADIF); // set ADIF bit to clear it
return adc_value; // return measured value
}
The getValue() function returns the current measured value, as with readValue(), but
checks beforehand in a “while” loop whether a new value is currently already present.
If this is the case, the measured value is returned immediately. Otherwise, the function
waits for the new measured value. This ensures that we do not read the same value
several times. (The new value can still be identical, of course, but it is a new
measurement.)
Usage
To use fast continuous measurement for own projects we need the functions that are
defined here after loop(). In setup(), as shown in the example, we should select the
input pin using analogSelect(), and the reference voltage that defines the measuring
range, and then use the freeRunning() function to specify the desired speed and thus
start continuous measurement.
Here, we can see how fast the measurement takes place for different values. The table
shows the respective duration per measurement in microseconds, as well as the
reciprocal value, i.e. the number of measurements per second. If possible, we should
not use the settings in the three red fields, as the measurement result may be quite
inaccurate.
289
In loop(), we now have various options for reading out the measured values. For
example, we can query whether a new value is present at each pass, and only if this is
the case, read out the value and process it further. For this, we call the function
valueAvailable() and, if necessary, readValue(). This way, we never have to wait for a
measured value. Without this check, readValue() always directly returns the last value
that was measured, regardless of whether it has already been queried.
For example, we can also request a new value at each loop pass using getValue(). In
this case, the analog-to-digital converter’s clock also determines the loop clock, as long
as a loop pass does not take longer than the measurement. However, in contrast to the
conventional analogRead(), the remaining loop sketch does not consume any additional
time, because it runs during the measurement.
Still, for both variants this applies: If the remainder of the sketch in loop() takes longer
than the analog-to-digital conversion, i.e. if the measurement is faster than the
program, individual measured values can be lost, of course. This can happen, for
example, when we do extensive floating-point calculations, or with large loops. An
EEPROM write, for example, takes a long time, as do serial outputs and, of course, the
use of delays.
By the way, we can change the input pin and/or the reference voltage at any time with
the analogSelect() function, even if the measurement is already running, that is, Free-
Running mode is already started. But, after that we should call getValue() twice
(without using the transferred value), because only then do we really get the values
with the new setting. After all, the currently-stored value and the measurement that is
currently being performed were both done under the old setting. When changing the
reference voltage, it may even be useful to make up to 10 (or even more) unused
dummy measurements, because, depending on how big the capacitor at the
microcontroller’s reference pin is, it may take e.g. a millisecond until the new reference
voltage is fully adjusted.
Instead of the input pin (A0 to A7) we can also specify a number with analogSelect(),
because there are three numbers with which we can make internal measurements:
For example, with the value 6, we measure the internal temperature (preferably with
the internal reference). We could even convert this to degrees Celsius, but it’s not worth
it because the tolerances are too high. We would not be able to measure accurate room
temperature anyway, since the microcontroller itself also heats up.
If we specify 12, we measure the internal reference voltage, which, of course, only
makes sense if the internal reference voltage is not also what we are using as our
measurement range. In section 13.1, we use exactly this measurement. There, the
internal reference is measured to determine the battery voltage.
290
13 could (theoretically) also be specified. But this makes little sense, because we
measure only GND and always get 0. It would only make sense if we wanted to test the
analog-digital converter to see if 0 (or maximum 1) is read out.
For example, if we have a button on our TV’s remote control that we don’t need, and
which doesn't do anything directly on the TV when it is pressed, then we can use that
button to control something completely different, e.g. to turn a lamp on or off.
Almost everyone still has one or another infrared remote control lying around
somewhere from no longer used (maybe even defective) devices. We can also give such
remote controls a second life using our universal receiver.
We need:
291
The program can even be taught several signals (i.e. several keys – even from different
remote controls), so that three different remote controls can be used for the same
function, for example. We can also use up to 10 channels separately, e.g. with the
numeric keys of an old, unused remote control.
And the best part is that, besides the Pro Mini board, we only
need a simple infrared receiver (VS1838B) that costs a few cents.
The photo shows the simplest setup ever. Even like this, the
remote control receiver is already functional.
Fig. 13.7b: VS1838B Fig. 13.7c: Example with transistor and solid-state relay
The circuit diagram shows an example with a transistor at Channel 1 (Pin 3) and a solid-
state relay at Channel 2 (Pin 4). Up to 10 channels can be used (Pins 3 to 12). How to
switch things with the outputs is described in detail in section 7. The circuit diagram is
only meant as an example.
Functionality
Almost all infrared remote controls send short pulse packets modulated at 38 kHz, so
the infrared LED flashes 38,000 times per second. The rapid swings in the following
picture show this modulation frequency. The solid blocks are the actual data pulses.
292
The lower graph is what the VS1838B infrared receiver makes out of it again. It
recognizes the modulation frequency (38 kHz) and reconstructs the actual data pulses,
which are present at its output, with a slight delay.
Our remote control receiver now does nothing other than scan this signal in short
periods (192 µs each) and write the respective durations into an array, i.e. the
alternating HIGH and LOW durations. As soon as there is no change for a certain length
of time (approx. 16 ms), the program assumes that the code is complete. In the array,
we then have the corresponding key’s code.
How the original devices define and interpret the code can be very different from device
to device, and certainly does not correspond with what is in our array. But, we don’t
care about that, because every code can be described using our method and recognized
later.
In Learning mode, the received code is then assigned to the current learning channel,
and then stored in the ATmega chip’s EEPROM. In Control mode, on the other hand, the
code is compared with the stored codes. If there’s is a match, the corresponding
channel is toggled, that is, switched on if the channel was off and off if it was on.
By the way, the switching states (which channels are currently on) are also stored in the
EEPROM, so that the Arduino still knows what was switched on – even after a power
failure, for example. It will then switch on exactly these channels again. If you don’t
want this, you can change the program sketch, so that everything is always switched off
in the setup, for example.
Likewise, you can change the switching method in the program code, of course: Instead
of the permanent change (on-to-off and off-to-on) we could also send only a single
pulse of a certain duration to the corresponding output for each received code,
depending on what you need done in your application.
293
First, Pin 2 is set as the input. Pins 3 to 12 serve as outputs for the individual channels.
EEPROM.h must be included, because we store the key codes (and the channels’
switching states) in the microcontroller’s EEPROM.
This is the variable section for the settings. The EEPROM size must match the
microcontroller used – 1024 bytes for the ATmega328, and half that for the 168, which
is unfortunately not enough to use all channels. eeprom_first_pos specifies the first
sequential EEPROM location to which the remote control codes are stored. This value
must be at least 10, because the 10 bytes before are used for the outputs’ switching
states.
The maximum code length quantifies the maximum memory consumption per code
learned. You can also adjust this value. (It doesn't matter if the code is truncated, as
long as the different codes remain distinguishable.) The minimum code length should
not be too short, or interference might also be interpreted as code.
pulse_tolerance specifies how much the pulses may vary in time to still be evaluated
as identical. error_tolerance, on the other hand, specifies how many incorrect
durations we will tolerate. At 0, we are very intolerant. With a much higher value (up
to max_code_len) the program would accept any key on the remote control (or even
other remote controls) as a match.
294
These are more variables that the program needs. If you want to prevent an accidental
re-learning of the codes, you can set learn_channel to 0 here.
void setup()
{
Serial.begin(38400); // prepare serial transmission
pinMode(fb_pin, INPUT_PULLUP); // input for remote signal
eeprom_pos = eeprom_first_pos; // to start position
for (n=1; n<=max_channels; n++) // the 10 switching channels
{
pinMode(n + 2, OUTPUT); // channels (pins 3 to 12) as output
if (EEPROM.read(eeprom_pos - n) == 99) // if channel was on
{
digitalWrite(n + 2, HIGH); // switch channel on
}
else // channel was switched off
{
digitalWrite(n + 2, LOW); // switch channel off
}
}
EEPROM.update(eeprom_size - 1, 255); // mark end of EEPROM memory
next_time = micros() + loop_time; // time for next loop pass
}
In setup(), serial transmission is set up first, so that you can see what the program is
doing in Serial Monitor. The input pin (which gets the signal from the VS1838B) is
switched to an input with an internal pull-up resistor. (According to the datasheet, the
VS1838B likes it this way, although it works without the pull-up, too.)
The EEPROM memory location is now set to initial position. In the “for” loop, the bytes
before this position, in which the previous channels’ switch states are stored, are then
read. 99 means switched on. (This is a value chosen arbitrarily, such that it’s very
unlikely that it would be in this state by chance.) In the “for” loop now, all outputs are
switched accordingly, so that e.g. even after a power failure everything is switched on
if it was on previously. If you don’t want this, you can also enter a LOW for both cases.
Then, 255 is stored in the last EEPROM memory location as an “end” character and the
variable next_time is set. loop_time sets the temporal clock as 192 µs.
295
void loop()
{
if (learn_channel) // if program is (still) in learning mode
{
if (learn_channel == millis() / learn_time) // learn channel based on system time
{
if (learn_channel == max_channels || channel < learn_channel) // finish learning
{
learn_channel = 0; // exit learning mode
digitalWrite(13, HIGH); // onBoard LED on
}
else // if another channel (for learning) follows
{
learn_channel++; // next channel
blink(3,100); // onBoard LED flashing 3 times 100 ms
}
}
}
In loop(), we first check whether the program is still in Learning mode, and whether
the next channel is already coming up, based on the system time. If this is the case,
Learning mode is terminated and the on-board LED is switched on if the current
channel was already the last one, or if no remote control signal was received for the
current channel, which can be recognized by the “channel” variables. Otherwise, the
channel number is incremented by 1 and the channel switch is indicated by three short
flashes of the on-board LED. For this, the blink() function is used, which is defined
after the loop() function.
These two lines create the constant temporal clock we need. The “while” loop has only
a semicolon at the end of it, instead of braces, meaning it does nothing except wait
until the difference between next_time and the system time returned by micros() is 0,
i.e. until the required duration is reached.
The second line increments next_time by loop_time (i.e. 192 µs), and thus sets the
new time for the next pass. Although system time is a long variable, we calculate and
compare everything here only using simple bytes. I will explain exactly how this works
in a moment, in the green Tip box on page 304.
296
Now, fb_time is incremented. This variable counts the loop passes since the infrared
signal’s last level change. Thus, the variable always contains the duration of the
current pulse (where a LOW signal could also be meant). If nothing has happened for
84 passes, then no signal is being transmitted at the moment, or the transmission is
complete. This distinction is made in the next comparison. If the amount of data
present is above the minimum code length, then a signal has indeed just been
received, or fb_data_size would still be 0.
297
So now, a signal has been received from the remote control and this whole part is
executed, provided that the program is still in Learning mode. We first check if there’s
still enough space in the EEPROM for the new code. Then we check whether the
channel is still up to date. If we are already at the next learning channel, a 254 is
written into the current EEPROM write position. 254 is our chosen character for “next
channel.” Then, the write position and “channel” are updated.
Now (before the actual code arrives) the length of the code, a number that is clearly
below 254, is written into the EEPROM, due to our maximum code length, so that
later, when reading, we can always differentiate whether it’s the character for “next
channel” or the actual code length). Actually, we’d have to increment the EEPROM
memory location by 1 again, but, since we begin writing code to the EEPROM from
position 1 (instead of 0) in the “for” loop that follows, it fits again. The first value
(position 0) cannot be used at all, because this is the time until the remote control
started to transmit. The value depends only on the moment when the key was
pressed, so it has nothing to do with the code yet.
Only after the “for” loop do we set the EEPROM location to the first free byte. There,
we write 255. This number means that no further data will come. (If another code is
stored, the 255 will be overwritten automatically at the next writing, and 255 written
again at a later position.) Then, another message is output along with the code, and
the on-board LED flashes briefly to signal that the code has been saved.
The “else” block still belongs to the check if the EEPROM memory space is sufficient.
If not, a corresponding message is output and the on-board LED flashes 20 times,
very rapidly.
298
This “else” block is what happens when the program is not in Learning mode. Here, we
deal with the actual key-code recognition. First, the EEPROM position and the channel
number, “channel,” is set to the beginning. Then, in the “while” loop, the EEPROM is
searched sequentially for codes, until we encounter 255, which is our “end” character.
If, on the other hand, 254 is in the current memory position, the channel number is
incremented so that the subsequent code is assigned to the correct channel if it
matches.
299
The next byte in the EEPROM is the length of the stored code. But, in fb_data_size, we
also have the length of the code just received, which we need for comparison, and we
must use the shorter of the two codes, of course. With some remote controls, for
example, the code length (at least with some keys) depends also simply only on how
long one presses it: As long as you don’t let go, transmission continues. We being from
position 1 again, comparing in the same way that we stored data in the EEPROM
before.
Byte by byte, the duration of the individual pulses and gaps is now compared in the
“for” loop, and the deviation is stored in the “difference” variable. A tolerance of ±1
must be allowed for in any case. With the value 2 as pulse_tolerance, we could be
even more tolerant, but it’s not necessary. After the “for” loop, the EEPROM memory
location is adjusted. (The previous location still contains the size of the code we just
used in our comparison.)
wrong_bits now contains the number of deviations from the code that exceed the
tolerance, i.e. the real errors, and there, the default setting doesn’t allow for any
deviations at all. If there are really no deviations[], we have found the code, and the
“channel” variable tells us which channel it is. The EEPROM location is now set to the
last byte, where there is always 255. So, the “while” loop (with which we search the
EEPROM) knows that the search is now over.
In the EEPROM, before the start position, we still use (up to) 10 bytes for the
individual channels’ present switch states. There, we look up whether the received
channel is already switched on, and then switch it off, or on if it was off. The new
switch state is again stored in the EEPROM, whereby only the value 99 counts as “on.”
Then, a serial message is output and the on-board LED flashes once briefly (300 ms)
to confirm the received signal.
If, on the other hand, the “while” loop is terminated without the position being set
(after a detected signal) to the last position, then the search was unsuccessful. We
have received a signal, but an unknown one, which is not stored in the EEPROM. In
this case, a corresponding message is also output and the on-board LED is flashed
briefly.
300
Now we have the section with the received code left. Here, we’re in the section where
no level change has taken place for some length of time. The pulse time and number
of received bytes is reset there, independently of whether a signal was actually
received.
In the “else” part, we now check whether a level change occurred. If this is the case
and the maximum code length has not yet been reached, the length of the pulse is
appended to the codes array and the pulse duration is reset to 0, because a new pulse
has now begun upon level change.
After the loop there is a small blink() function, to which you pass two values – how
often it should blink and for how long. The first delay is preceded by a condition, so
that it is only executed between the light pulses and not at the very beginning or at
the very end.
301
Possibilities
Basically, we can use one, two, or up to 10 channels, each of which we teach with
different key codes from one or more remote control(s). To be absolutely sure that a
key signal will be recognized reliably later, we should possibly send it twice during the
learning phase. Some remote controls have e.g. a small code portion in two versions,
which are always sent alternately. If the receiver is taught once, it would only respond
to every second keystroke. With other remote controls, the receiver works perfectly
even if each key is taught only once.
In total, the ATmega328 has an EEPROM memory of about 1000 bytes available for the
codes. Each code consumes up to 70 bytes according to the variable max_code_len,
and, with many remote controls, the code is at least that long. That means: 14 learning
operations are possible. If we want to use all 10 channels and teach each channel twice,
we have to reduce max_code_len to 50. This probably works fine with most remote
controls. But, too-short a stored code also bears the risk that not all codes (i.e. all
keys) are distinguishable anymore. If in doubt, we have to try this out. Often, it’s
sufficient to store the key codes just one time. Then, we could even increase
max_code_len to 100 and still use all 10 channels.
Besides using all 10 channels, there is also the possibility of assigning several keys (or
even different remote controls) to a single channel (e.g. the first one). We can even do
this with two or three channels, as long as the EEPROM memory is sufficient.
After switching on, we have 10 seconds to send the signal (or signals) for Channel 1. I
recommend pressing the corresponding remote control button briefly from 1 meter
away, and, when the signal has been stored (as indicated by the on-board LED lighting
up briefly) we press the same button again a little longer. This way, we have stored the
signal twice, and it should always be reliably detected. (Within the 10 seconds we could
now also send other signals by using other keys or even other remote controls, all of
which should later be interpreted as Channel 1.)
302
When the 10 seconds is over, the on-board LED flashes 3 times. This means the channel
has changed. Now, we can send and store one or more signals for Channel 2 in the
same way. Then (when the LED blinks 3 times again), it’s Channel 3’s turn… and so on.
If we do nothing for the entire 10 seconds, i.e. send no signal, the program assumes
that programming is finished. It does not jump to the next channel after 10 seconds has
elapsed, but ends Learning mode. If the LED flashes 20 times very fast, it means the
EEPROM is full!
If Learning mode is complete, you can see this by the fact that the on-board LED is
illuminated constantly – but only until a signal is sent again, which is then compared
and recognized if necessary. Now, all keys for all channels should be recognized
correctly and the outputs should switch accordingly.
There is the danger that after successful teaching, a key signal is accidentally sent
within the first 10 seconds after power-on or a reset, because then all stored codes are
lost and Learning mode starts again (with the code just sent as Channel 1). To prevent
this, we can set the variable learn_channel at the beginning of the code to 0 (instead of
1), and then reload the code. Learning mode is then skipped. To change any codes, of
course, we have to go back to the original version. A more comfortable solution would
be a push-button, e.g. A0 to ground. We have to define A0 as an input with a pull-up
resistor in setup(), and then query it, so that the variable learn_channel is only set to 1
in setup() when the push-button is pressed (LOW), otherwise it is set to 0. This way,
you simply have to hold the button pressed during and shortly after the reset to enter
Learning mode.
For even timing of loop passes—as used in the previous sketch—here are some more
tips on the next pages:
303
In this example, loop() is executed every 192 µs. The “while” loop has only a
semicolon at the end, instead of the usual block delimited by braces. Thus, the loop
does nothing except wait until the difference between next_time and micros() is 0,
i.e. until the time next_time is reached.
The next line increases next_time by loop_time (192 µs), and so sets the next time.
There are constantly overflows, but they are no problem at all, because although the
system time is a long value, we calculate and compare everything here using simple
bytes. This is possible because the number 192 (our time unit) fits into one byte. By
specifying byte, we interpret the calculated difference as a byte only. The condition of
the “while” loop is thus always a number running backwards from 192 toward 0, and
only the value 0 is considered false. Even if the system time overflows, everything
fits. The loop is always executed exactly every 192 µs (a good 5000 times per
second).
To keep to this time period, the content of the loop must not be too large. If the
execution takes longer, the duration cannot be met, of course. In the following loop,
the waiting time will then be set to 255 µs. So, in the worst case, the waiting time of
255 decrements toward 0, because we evaluate only one byte. After that,
everything’s back in sync. (In the case of the remote control receiver, for example,
this is the case when a key code has arrived, but, since no code is being read at that
time, we don't need this temporal precision).
The cycle time must always be divisible by 4 (or better 8), because the system time,
micros(), runs in steps of 4. With a clock time of 191, for example, we would wait
endlessly, because the difference would never be 0. In the 8 MHz version, it’s even
steps of 8.
304
If the execution of the loop takes longer, so that the time period cannot be met, the
waiting time is now set to 65,535 µs, i.e. a good 65 milliseconds. This is the longest
clock time we can use in this way.
For even longer times, we can use the other system time, millis(), instead of
micros(). Then, the times are 1000 times longer, and there, nothing must be divisible
by either 4 or 8.
305
Again, we always get the remaining waiting time as the result of next_time –
int(micros()). But, we only stay in the “while” loop as long as the result is positive. If
the time was exceeded, the value is below 0 and we continue immediately, but the
original duration is kept. That means, in the following loop passes, the lost time is
made up again until everything back in sync.
This variant also has another advantage: With 8 MHz system clock especially, it may
happen that the zero-crossing is missed because the calculation alone takes longer.
This can’t happen in this example. With the 7-segment control from section 11.2.1,
for example, which is predominantly operated on 8 MHz, we use this kind of clocking.
306
while (micros() & 240); // Wait until the next 256 µs has passed
Yes, it really needs only this one line. 240 is 11110000in binary. Using the &
operation, only the bits set when the number is 240 are considered. These are the
lowest 8 bits, of which the last 4 are ignored again, to be sure that this time is not
missed, even at an 8 MHz clock frequency.
In each case, as soon as the time periods 0, 256, 512, 768, 1024, 1280, etc. are
reached, the bottom 8 bits of the system time are all 0. Thus, the “&” also operation
results in 0 and it goes on with the next loop procession.
while (micros() & 112); // wait until the next 128 µs has passed
while (micros() & 240); // wait until the next 256 µs has passed
while (micros() & 496); // wait until the next 512 µs has passed
while (micros() & 1008); // wait until the next 1024 µs has passed
while (micros() & 2032); // wait until the next 2048 µs has passed
while (micros() & 4080); // wait until the next 4096 µs has passed
while (micros() & 8176); // wait until the next 8192 µs has passed
while (micros() & 16368); // wait until the next 16,384 µs has passed
while (micros() & 32752); // wait until the next 32,768 µs has passed
These are further clocking values that we can use. The numbers are always powers of
two minus 16. Even longer times (e.g. using “& 65520” or “& 131056”) are possible.
If a loop takes too long, it simply continues at the next but one time unit.
307
We need:
Much here is identical with the sketch from section 10.2.5. That’s why focus on
explaining the additions. Besides the “oversampling” variable, which is responsible for
the sensor’s internal oversampling, we now have the value over_oversampling, with
which we again add many values together, and later form the average value. 0 stands
for a single measurement and each increase of 1 doubles the number of
measurements, and thus also the time until output.
Furthermore, we have the variable comp_count, as well as the array alt_before[] and a
few more additional variables, which we need later in the loop.
308
void setup()
{
Wire.begin(); // prepare I2C transmission
In setup() the I²C transfer is initiated, and the calibration data is read (same as in
10.2.5). In addition, serial transmission (to the PC) is initialized.
void loop()
{
p_added = 0; // reset
for (n=0; n<((unsigned int)1<<over_oversampling); n++) // many measurements
{
get_t(); // read temperature value
get_p(); // read out pressure value
calculate(); // calculate pressure and temperature from raw data
p_added += pressure; // sum up air pressure
}
In loop(), the reading of the temperature and pressure data, as well as the calculation,
is now in a “for” loop, where over_oversampling determines the number of
measurements. In p_added, the calculated air pressure is summed. (One could also
have added the raw measured data and run calculate() only once with the average, but
that would be less accurate and give a coarser result.)
309
Then the height, “altitude,” is calculated from the added pressure values (which
unfortunately does not quite fit into one line here in the book) because the divisor,
101,325, has to be shifted by a few binary digits with the corresponding
over_oversample setting, which corresponds to a factor of 2 per binary digit. (This is
comparable to adding zeros or shifting the decimal point in the decimal system, which
represents a factor of 10 there.)
The variable “altitudes” always contains the sum of the last “altitude” values to form
the average, so the current “altitude” value can be compared with the
previously-displayed values to detect altitude changes. At the very first loop() pass,
however, “altitudes” is still 0. In this case, the variable “altitudes” is assigned the
current altitude, but multiplied by comp_count, the number of comparison values. The
alt_before[] array, also still empty (which would usually always contain the previous
“altitude” values) is first completely filled with the first “altitude” value according to the
value of comp_count. Thus, “altitudes” is the sum of all array entries.
Now, with “delta_alt,” the altitude difference is calculated. For this, “altitude” is
multiplied by the variable “comp_count,” and then “altitudes” is subtracted from it. The
result is then divided by “comp_count” again. The additional value (comp_count >> 1)
corresponds to half of comp_count and is added before the division so that the result is
rounded up from 0.5.
310
Now the variable “counter” is incremented and reset if the value comp_count is
reached, because it should count the loop passes for the array position. Then the
current value is added to “altitudes” and the oldest value from the alt_before[] array
(corresponding to “counter”) is subtracted. The new “altitude” value is now added to
this array cell. This way, the array always contains the last altitude values according to
comp_count, and “altitudes” always contains the sum of the array values, without
having to read out the whole array and add all values on each pass.
[...]
Now come the functions get_t(), get_p(), calculate() and read_int() We can skip these
here, because they are identical to the sketch in section 10.2.5.
311
a further zero value, we set comp_count to only 1. With a higher value of 5, it takes
e.g. 5 further outputs until the new altitude counts completely as a zero value, because
the zero value is then formed from the last 5 height values. Depending on the weather,
however, there are constant fluctuations of the output by a few centimeters, even when
there’s no wind.
But, it would not make sense to measure much longer with even more single
measurements to detect altitude changes as accurately as possible, because this would
paradoxically increase the errors significantly. Weather changes alone would result in
much larger deviations after just a few minutes. However, we can take advantage of
this phenomenon for a completely different purpose:
Possible extensions
If you want, you can build a complete barometer based on this program sketch. Using
stepper motor 28BYJ-48 from section 9.2.2 (page 174) you could attach a physical
indicator that displays the air pressure, for example. We could also extend the circuit
with a text display from section 11.3, and thus display temperature, air pressure, and
the weather trend. With an additional sensor for humidity, we would even have another
value.
The only problem is the continuous (albeit very low) power consumption, which makes
a battery-powered version difficult, because such a barometer should run for months or
years. A small power supply (e.g. 5 V / 100 mA) solves the problem. Another possibility
would be the sleep mode from section 13.2, which switches off the Arduino after a few
seconds. But then you would have to press the Reset button (or another button) every
time you want to read the data.
313
314
The air pressure sensor delivers a maximum of about 100 temperature and air pressure
values per second. So, we could generate a stereo audio file (in PCM WAV format with
the .wav file extension) from this data, over minutes, hours, or days, recording the air
pressure on the left channel and the temperature on the right channel. We set the
regular playback frequency (sampling rate) to 44,100 Hz, for example. This is a
common value that’s also used for audio CDs and many other things.
This way the file will be played back later (e.g. on a laptop or with an audio player) at
441 times the normal speed, and we can hear infrasound! With the original sampling
rate of 100 Hz when recording, we can record all frequencies below 50 Hz. An infrasonic
sound at 10 Hz becomes 4.41 kHz when played back. 1 Hz becomes 441 Hz, etc.
Theoretically, there are no limits below that.
However, it is much more interesting to use an audio editing program to look at the air
pressure and temperature waveforms later – especially if the recording ran over several
days. The left channel then shows us the air pressure graph, and the right the
temperature graph.
315
Fig. 13.9a: Graph progression for air pressure (above) and temperature (below)
Here, I have simply recorded over almost 3 days, opened the finished .wav file with an
audio editing program, and then normalized both channels separately, amplified as
much as so that the full range is used. The upper graph with the pressure readings still
looks a bit thick and frayed here due to the fact that each pixel on the X -axis consists of
tens of thousands of measurements, and these also contain some noise and thus
fluctuate to a certain degree.
Fig. 13.9b: Graph progression of air pressure (above) and temperature (below)
Here, the recording was resampled and 4000 values were averaged into a single value –
similar to the way we’ve often averaged many measurements before. The noise is gone,
and we can see the pressure and temperature curves more clearly.
316
From the graphs, we can now see a few things: From the pressure graph at top, in
terms of time, it became somewhat windier in the second half of the measurement, as
many small fluctuations increase there. In a real storm, the fluctuations would be even
more extreme.
The temperature curve at bottom clearly shows the day-night rhythm. The almost
continuous small spikes are the thermostat switching cycles of the room’s heating. One
could count how often the thermostat had switched on and off during these 3 days.
Further, one recognizes two quick downward deflections: the window was opened for a
short time.
Construction
We need:
317
Since we combine the air pressure sensor with the SD card module, we have to include
three libraries: the I²C interface for the air pressure sensor, the SPI interface for the
SD card module and the special SD card functions.
Then, we define two pins where we can attach a push-button to start and stop the
recording. Pin 3 is switched to LOW and serves as a ground pin for the button. This is
very practical, because the button’s two pins are then right next to each other. Of
course, we could also use the real ground (GND) instead of pin 3.
The other definitions and variables, as well as all program parts we already know from
the air pressure sensor and the SD card sketch, are not shown here. We are now only
interested in what concerns the recorder, specifically.
int duration[4] = {5000, 8000, 14000, 26000}; // times (µs) according oversampling
int t_duration = 5000; // time for temperature measurement
[...]
Here, we have the recorder settings. The variable “oversampling” also determines the
number of pressure measurements. To record 100 values per second, this setting must
be 0. audio_rate, on the other hand, specifies how many values per second will be
delivered when we later play back the recording as an audio file. max_file_size
determines the maximum recording length. However, the 10 minutes refers to the
playback time – the corresponding recording time is several days. However, we can
adjust this value almost arbitrarily. When the maximum file size is reached, the file is
closed and a new recording is started.
The “duration” variables determine the delay between measurements (just as with the
altimeter). However, the times here are 500 µs longer, because we do not work with
delays, but rather with a fixed clock here, during which the Wire instructions for
temperature and pressure measurement are also executed.
318
unsigned int next_time; // system time at which the data are available
long zero_value; // pressure zero line
long record_value; // pressure value to save
long zero_temp; // temperature zero line
long temp_value; // temperature value to save
boolean must_record = false; // indicates whether recording is activated
boolean is_recording = false; // indicates if recording is currently running
boolean pressed = false; // indicates whether pushbutton is pressed
unsigned long file_size; // actual file size
unsigned int file_number = 0; // actual file number
byte state_counter = 0; // counts how long button is not pressed anymore
File wave_file; // recording file
After the special variables for temperature and pressure calculation, which we skipped
here again, these variables will be needed later for the recorder.
void setup()
{
pinMode(record_pin, INPUT_PULLUP); // switch pin on input with pullup
pinMode(low_pin, OUTPUT); // low pin on output ...
digitalWrite(low_pin, LOW); // ... and LOW
[...]
setup() starts with the definition of the pins for the push-button. Due to the active
internal pull-up resistor, we do not need any further components.
void loop()
{
get_t(); // read temperature
get_p(); // read pressure
}
[...]
In loop(), we now have only the alternating calls of temperature and pressure
measurement, because in order not to lose time, we do not wait there in each case
with a delay for the data, but use the waiting time for all further tasks. For the
temperature measurement, function calculate() is called while we wait for the result.
For the pressure measurement, we call the recording() function. We will, therefore,
skip these two functions, because we already know the temperature and pressure
measurement.
319
void calculate() // calculate temperature (in tenth degree) and pressure (in pascals)
{
if (p) // if pressure has already been measured before
{
[...]
}
if(!zero_value) // if zero value has not been determined yet
{
if (!pressure) return; // cancel if no measurement has been taken yet
zero_value = pressure; // actual value as zero line
zero_temp = b5; // actual value as zero line
}
record_value = pressure - zero_value; // pressure value for recording
if (record_value > 32767) record_value = 32767; // max value if clipping
else if (record_value < -32768) record_value = -32768; // negative clipping
temp_value = b5 - zero_temp; // temperature value for recording
if (temp_value > 32767) temp_value = 32767; // max value if clipping
else if (temp_value < -32768) temp_value = -32768; // negative clipping
}
Now, the calculate() function. The actual temperature and actual pressure are first
determined from the measured values, as long as the pressure has already been
measured. I have abbreviated this now again with [...], because I’m sure no one wants
to read these calculations (with the many formula lines from the data sheet of the
BMP180) again.
Then, we check whether the zero base lines for pressure and temperature are already
defined. If this is not the case, the first measurement is now defined as the base value.
If, on the other hand, no measurement has yet been made, we exit the function
completely.
Otherwise, the values are now prepared for recording. For this, we subtract the
pressure zero value from the pressure. If the value is overridden, it is limited to the
permissible range of -32,768 to 32,767. We then do exactly the same with the
temperature. Thus, both values are now ready for recording.
In the recording() function, two conditions are first checked: whether the recording
should run (because it is switched on and has also not yet reached the maximum file
size), and whether the recording is actually running. These two distinctions now result
in 4 possibilities. In the first case, the recording should be running, but it is not
running yet:
320
Here, a new recording is started. For this, the file number is increased in the do-while
loop until this (together with the further text) results in a file name that does not exist
yet. A new file is then opened with the corresponding file name, and a corresponding
message is also output serially. In case of an error, another message is output and the
process is stopped by means of an endless loop.
321
Here, the header for the wave file is first written. Most of it is explained in the
comments, where the least-significant byte always comes first for all values.
If you want to know exactly how such a file is structured, you can find it on
Wikipedia under WAV.
Now the variable for the file size is updated and we check whether it corresponds to
the regular header size of 44 bytes. If this is the case, an OK message is output
serially. Otherwise there’s an error message and the file is closed again. In addition,
the rest of the process is halted using an endless loop.
Now this is the part when the recording is already running and should continue.
The current recording values (two bytes for the pressure and two bytes for the
temperature) are simply written to the card and file_size is increased by 4 bytes.
322
If the recording was deactivated but is still running at the moment, the file must now
be closed. To do this, the file size is first checked. If an error occurs, a warning is
issued, but the program continues to run.
Before the file can be closed completely, a value with 4 bytes each must be entered at
two positions of the header. (When opening the file, we merely entered 255 for these
bytes.) At positions 4 to 7, the remaining file size comes from position 8, so 8 less. At
position 40 to 43, the remaining file size also comes from position 44. With both
actions, in case of error (if the position cannot determined) a warning is output instead
of the write operation.
323
Then, the pointer is set to the end of the file again and the file is closed. This is then
also reported serially, and is_recording and the file size are reset. Finally, we may have
the case that recording is not running and should not run at all. Then, there’s obviously
nothing to do.
The pushbutton is now read and we check and whether its status matches the stored
status (pressed or not pressed). Only if there’s a deviation (i.e. if the button has since
been depressed or released), is this part executed. If the button was pressed,
must_record is toggled, that is, switched on if recording was off, and off if it was on. If
the button was released, however, this is not registered immediately, but a counter is
first started. Only after 20 further passes is “pressed” set to false. This serves for
debouncing, so that the recording isn’t started and stopped several times within
fractions of a second accidentally.
324
Usage
At the beginning of the sketch we can set three variables. “oversampling” should be set
to 0 to use the fastest recording frequency of 100 Hz. “audio_rate” specifies the later
playback frequency. There, we can also use a value lower than 44,100, so that the time
factor (44,100 / 100 = 441) is not quite so high. Common values are 8000, 11,025,
16,000, 22,050, 32,000, 44,100, and 48,000. Not every player can play other
frequencies. With max_file_size, we define the maximum file size. 105,840,044
corresponds to a playback time of 10 minutes at 44,100 Hz, but a recording time of
more than 3 days.
We should then “normalize” the completed recording, that is, increase the volume of the
signal so that the full range is used, but without exceeding the maximum. Without
normalization, the signal will be too quiet. Of course, we could add a gain factor when
recording, but we don't know beforehand how much the temperature and air pressure
will vary during recording. Normalization can be done with audio editing programs in a
few clicks. It should be done separately for both channels. With such a program, we can
then also view the recording as measurement graphs. The left channel shows the course
of the air pressure during recording, while right channel shows the temperature.
Weather recorder
If we change the “oversampling” value to 3, only a good 32 (instead of 100) values per
second will be recorded, which is still far more than enough than we need for weather
recordings. We could also combine the altimeter (section 13.8) with the SD card
functions, to write a corresponding text line into a file for each serial output, as well.
This way, we get the weather recording as a text file.
So, you see, there are countless possibilities to what you can do, and the individual
chapters offer—besides the concrete projects—a lot of information and suggestions,
which should help you in implementing as many of your own ideas as possible.
Unfortunately, we have now reached the end of the book, and I wish all readers a lot of
fun in constructing, measuring, and controlling!
325
326
Index
1 - 9 Bipolar transistor....................................... 99
16 MHz version.......................................... 16 Bit shifting................................................. 119
18650 cell..................................................260 Bitwise operators.................................... 119
28BYJ-48................................................... 174 Blinking light............................................... 54
3950 type.................................................. 195 BMP180............................................ 211, 308
8 MHz version............................................. 16 Board..................................................... 14, 56
Bool................................................................ 60
A
Bootloader................................................... 55
Acceleration.............................................. 219
BPW34........................................................ 199
Accelerometer..........................................219
Breadboard.................................................. 47
ADC................................................................ 65
Breakout boards......................................211
Air pressure sensor................................211
Brightness control...................................108
AliExpress.................................................... 30
Brightness measurement...........193, 199
Altimeter..........................................212, 308
Brushless motor......................................188
Altitude difference.................................. 308
Buyer Protection.........................29, 30, 31
Amazon......................................................... 29
Buzzer........................................................... 44
Analog........................................................... 65
Byte............................................................... 60
Analog-to-digital converter.................... 65
AnalogRead().............................................. 66 C
AnalogReference().................................... 66 C++............................................................... 53
AnalogWrite()...........................................109 Calibration............................................ 66, 75
AND............................................................. 119 Caliper........................................................... 46
Anode.......................................................... 230 Capacitor...................................................... 42
Antenna......................................................229 Car battery................................................125
Arduino website......................................... 63 Card module...................................256, 317
Arduino-platform....................................... 53 Cathode...................................................... 230
Arduino-Software...................................... 53 Ceil................................................................. 69
Array.............................................................. 90 Center tap................................................. 173
ASCII character....................................... 250 Char............................................................... 60
ATmega......................................................... 14 Character set............................................250
ATmega168.......................................... 15, 16 Characteristic curve.......................... 83, 99
ATmega328.......................................... 15, 16 Charge controller.............................41, 264
Charge voltage........................................ 149
B
Charger...................................................... 154
B 25/50....................................................... 195
Charging current........................... 131, 149
Back-light.................................................. 250
Charging duration...................................151
Balancer..................................................... 263
Charging regulators...............................264
Barometer................................................. 313
Charging station......................................127
Basic equipment........................................ 34
Charging voltage..................................... 262
Battery............................................. 131, 260
Cheat.......................................................... 285
Battery level............................................. 265
Clock........................................................... 304
Battery protection........................... 41, 262
Code..................................................229, 291
Battery test............................................... 127
Coded connection...................................... 52
Battery-operated............................. 15, 274
Coding........................................................ 229
Baud rate................................................... 151
Coil.................................................... 106, 172
BC337 transistor.....................................101
Color.................................................. 109, 111
BC547 transistor.....................................100
Comment.............................................. 54, 75
BD435 transistor.....................................101
Commenting out........................................ 75
Bipolar stepper.............................. 171, 183
Common anode....................................... 230
327
328
329
S T
Tau (τ)........................................................ 114
Sampling rate........................................... 315
Saturation.................................................. 133 Temperature graph................................. 315
Temperature measurement.......194, 246
Saturation voltage........................ 101, 104
Scam............................................................. 32 Template.......................................................88
Text display............................................... 250
SCK.............................................................. 256
Text LCD..................................................... 250
SCL.............................................................. 207
SD card............................................ 256, 315 Thermal radiation.................................... 205
Thermally conductive adhesive..........104
SDA............................................................. 207
Search settings.......................................... 29 Thermistor................................................. 194
Thermometer........................................... 246
Segment.................................................... 230
Thermostat................................................ 249
Sensor........................................................ 193
Serial............................................................. 21 Threshold......................................... 168, 249
Threshold current.................................... 255
Serial interface...............................207, 256
Serial.begin................................................. 68 Thumbtack......................................... 86, 129
Thumbtack technique...............................49
Serial.print...................................................68
330
TL1838....................................................... 199 V
Tolerance.............................................. 42, 78 Variable......................................................... 60
Tools.............................................................. 34 VCC........................................................ 16, 65
Transistor.............................................. 43, 99 Vernier caliper............................................ 46
Transistor array....................................... 104 Voltage divider.......................71, 193, 244
Transmission speed................................... 68 Voltage regulator............................. 16, 274
True................................................................ 60 Voltmeter...................................................244
TWI.............................................................. 207 VS1838B.................................................... 199
U W
UART...................................................... 21, 68 Wake up..................................................... 269
ULN2003A transistor array..................104 WAV format............................................... 315
ULN2803A transistor array..................105 Waveform.................................................. 315
Ultrasonic................................................... 201 Weather...................................................... 212
Unipolar stepper............................171, 174 Weather trend.......................................... 313
Universal remote control......................291 While loop.................................................... 63
Uno board.................................................... 18 Wire............................................................... 23
Unsigned...................................................... 60 Write()........................................................ 258
USB................................................................ 41
Z
USB adapter................................................ 21
Zero base line.......................................... 320
USB charging regulators.......................264
Zero-crossing........................................... 107
USB connector............................................ 20
331