0% found this document useful (0 votes)
404 views

Arduino & Co - Measure, Control, and Hack

The document discusses projects using Arduino Pro Mini boards including building a voltmeter, thermometer, laser cutter controller, and more. It provides the necessary knowledge to create additional projects and covers topics like measuring, switching loads, motors, sensors, and programming the boards.

Uploaded by

Al K
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
404 views

Arduino & Co - Measure, Control, and Hack

The document discusses projects using Arduino Pro Mini boards including building a voltmeter, thermometer, laser cutter controller, and more. It provides the necessary knowledge to create additional projects and covers topics like measuring, switching loads, motors, sensors, and programming the boards.

Uploaded by

Al K
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 332

books

books books

Arduino & Co Arduino & Co

Arduino & Co – Measure, Control, and Hack • Robert Sontheimer


Measure, Control, and Hack
Clever Tricks with
ATmega328 Pro Mini Boards
Measure, Control, and Hack
With a simple Pro Mini board and a few other components, projects that Clever Tricks with
ATmega328 Pro Mini Boards
20 or 30 years ago were unthinkable (or would have cost a small fortune)
are realized easily and affordably in this book: From simple LED effects #define measure_pin A3 //
to a full battery charging and testing station that will put a rechargeable Robert Sontheimer was Measuring input (A0 and A1 us
through its paces, there’s something for everyone. immediately on board when the ed
first home computers arrived in float r_ntc25 = 50
our living rooms some 40 years 000; // resistance
All the projects are based on the ATmega328 microcontroller, which offers ago, with his ZX81 and C64. Back float r_fixed = 47 of the NTC at 25
endless measuring, switching, and control options with its 20 input and then, he converted a plotter into a 000; // fixed resi d
float r_factor[33] stor (example 47
output lines. For example, with a 7-segment display and a few resistors, scanner, among other quirky and
= // R-factor at ki
you can build a voltmeter or an NTC-based thermometer. The Arduino original ideas, and today he uses
5°) NTC version 39 -30 to +130 degree
platform offers the perfect development environment for programming
Arduino Pro Minis to control entire
50
CNC laser machines and has even
{17.3, 12.8, 9.59,
this range of boards. invented a matching suction system:
7.25, 5.51, 4.23,
his “self-changing toilet paper filter.” 0.81, 0.656, 0.535, 3.27, 2.54, 1.99,
Besides these very practical projects, the book also provides the necessary In his office, he has a magnet that’s 0.438, 0.36, 0.3, 1.
been levitating for years – controlled 0.107, 0.091, 0.07 0.25, 0.21, 0.176,
knowledge for you to create projects based on your own ideas. How to
79, 0.0671, 0.0585 0
, 0.0507, 0.0441,
by a Pro Mini, of course.
measure, and what? Which transistor is suitable for switching a certain Just a regular freak. 0.0294}; 0.03
load? When is it better to use an IC? How do you switch mains voltage?
Even LilyPad-based battery-operated projects are discussed in detail, as
well as many different motors, from simple DC motors to stepper motors. byte digits = 4;
// number of digi
boolean common_ano ts
Sensors are another exciting topic: For example, a simple infrared receiver
de = false; // fa
that can give disused remote controls a new lease on life controlling your byte segment_pin[ lse for common ca
home, and a tiny component that can actually measure the difference in 8] = {11,A1,6,8,9 thod
byte digit_pin[6] ,12,5,7}; // a,b,
air pressure between floor and table height!
= {4,A0,13,10}; // c,d,e
last to first digi
t
int frequency = 20
0; // frequency of
float change_frequ display in Hz (abo
ency = 0.5; // re ut
byte segments[20] fresh display ever
= // bit pattern y 2
Elektor International Media BV
(a to g) for the di
www.elektor.com ffer
B1111110, B0110000
, B1101101, B11110
B1011011, B1011111 01, B0110011, //
, B1110000, B11111 0
B1110111, B0011111 11, B1111011, //
,Robert
B100Sontheimer
1110, B0111101, B1 5,
001111, // A,
Arduino & Co – Measure, Control, and Hack

o This is an Elektor Publication. Elektor is the media brand of


Elektor International Media B.V.
PO Box 11, NL-6114-ZG Susteren, The Netherlands
Phone: +31 46 4389444

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.

o British Library Cataloguing in Publication Data


A catalogue record for this book is available from the British Library
ISBN 978-3-89576-515-5 Print
ISBN 978-3-89576-51 - eBook

o © Copyright 2022: Elektor International Media B.V. (2022-08 / 1st)


Prepress Production: Robert Sontheimer
English translation: Brian Tristam Williams (and the author)
Printed in the Netherlands by Ipskamp Printing, Enschede

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.

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 2 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 3 2022-07-27 20:20


Table of Contents

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 4 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

6.1 Reading inputs digitally ...................................................................................58


Syntax: Variables............................................................................................................. 60
Syntax: pinMode(), digitalRead() and digitalWrite() ..........................................61
Syntax: Comparisons and conditions....................................................................... 62
Syntax: while and do-while loops.............................................................................. 63
Pushbutton switch............................................................................................................ 63
Sketch: Pushbutton switch........................................................................................... 64
6.2 Reading analog inputs..................................................................................... 65
Reference voltage............................................................................................................ 65
VCC as reference.............................................................................................................. 65
Internal reference............................................................................................................ 66
External reference.............................................................................................................. 66
Syntax: analogRead() and analogReference() ...................................................... 66
6.2.1 Measuring voltages directly .............................................................67
Syntax: Defining constants............................................................................... 67
Syntax: Serial transmission.............................................................................. 68
Syntax: Calculations and assignments......................................................... 68
Calculation pitfalls.................................................................................................. 69
Syntax: Rounding up and down...................................................................... 69
Sketch: Measuring voltages up to VCC ........................................................ 70
Calibration................................................................................................................ 71
6.2.2 Measuring using internal ref. & voltage divider ........................71
Possible ranges...................................................................................................... 72
Sketch: Measuring using internal ref. & voltage divider ........................72
Calibration................................................................................................................ 74
6.2.3 Measuring directly using the internal reference .......................74
Tip: Commenting out lines................................................................................ 75
Calibration................................................................................................................ 75
6.2.4 Measuring current...............................................................................76
Sketch: Measuring current................................................................................ 76
Possible ranges...................................................................................................... 78
Calibration................................................................................................................ 79
6.2.5 Resistor measurement.......................................................................80
Sketch: Resistance measurement.................................................................. 81
Swapping resistors................................................................................................. 82
Calibration................................................................................................................ 82
6.3 Switching outputs digitally .............................................................................82
Chapter 7 • How do you switch something? ..............................................................83
7.1 LEDs....................................................................................................................... 83
7.1.1 Calculating the series resistance .................................................................... 84
7.1.2 LEDs in battery operation.................................................................................. 84
7.1.3 Switching direction to ground or positive ................................................... 85
7.1.4 Project: LED effect board..................................................................86

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 5 2022-07-27 20:20


Table of Contents

Determining resistor vales................................................................................... 87


LilyPad construction template .......................................................................... 88
Pro Mini construction template ........................................................................ 89
Syntax: Arrays....................................................................................................... 90
Syntax: for loop.................................................................................................... 90
Syntax: delay() and system time................................................................... 91
Simple LED effect................................................................................................. 92
Sketch: LED rotation effect.............................................................................. 92
Syntax: Random values with random()....................................................... 94
LED running light effects ................................................................................... 94
Sketch: LED running light effects................................................................... 95
Other Blink applications........................................................................................ 97
7.1.5 Battery protection for effect flasher.............................................................. 97
7.1.6 LEDs with integrated series resistor .............................................................. 98
7.1.7 Power LEDs............................................................................................................. 98
7.2 Switching using a transistor.......................................................................... 99
7.2.1 BC547 transistor................................................................................................ 100
7.2.2 BC337-40 transistor.......................................................................................... 101
7.2.3 BD435 transistor................................................................................................ 101
Tip: Heat test...................................................................................................... 102
7.2.4 Switching using MOSFETs............................................................................... 102
The NTD4906N and IRLR8726PbF ............................................................... 103
Tip: Thermally conductive adhesive ........................................................... 104
7.2.5 ULN2003A transistor array............................................................................. 104
7.2.6 ULN2803A transistor array................................................................................. 105
7.3 Switching using a relay.................................................................................106
7.3.1 Solid-State relay................................................................................................ 107
Chapter 8 • Controlling, regulating, and dimming ................................................ 108
8.1 Pulse-width modulation (PWM).................................................................108
Syntax: analogWrite()................................................................................................. 109
8.1.1 Project: Dimming LEDs in all colors............................................109
8.1.2 Quick color theory................................................................................................ 110
8.1.3 Flowing color changes...................................................................................... 111
Syntax: sin() and cos()................................................................................... 111
Sketch: Flowing color changes..................................................................... 112
Tip: Small test with LED effect board ........................................................ 113
8.2 Low-pass demodulation................................................................................ 114
Time constant τ.............................................................................................................. 114
Calculation........................................................................................................................ 115
Ripple................................................................................................................................. 115
Two-stage filter............................................................................................................... 116
8.3 Regulation with a feedback loop .................................................................................. 116
8.4 Project: Adjustable constant current source .........................................116

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 6 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 7 2022-07-27 20:20


Table of Contents

9.2.3 Control using the A4988................................................................................. 183


Pinout...................................................................................................................... 184
Adjusting the current.......................................................................................... 185
9.2.4 Control using the DRV8825 ............................................................................ 186
Pinout...................................................................................................................... 186
Adjusting the current.......................................................................................... 187
9.2.5 Version A4988 vs. DRV8825.............................................................................. 188
9.3 Brushless motors.............................................................................................188
9.3.1 Control using ESC.............................................................................................. 189
Power supply........................................................................................................ 189
Control signal........................................................................................................ 190
Sketch: ESC-control using potentiometer ................................................ 190
9.4 Servos................................................................................................................. 192
Control............................................................................................................................... 192
Chapter 10 • Sensors...................................................................................................... 193
10.1 Analog sensors................................................................................................................. 193
10.1.1 Brightness sensor using LDR...................................................... 193
10.1.2 NTC temperature measurement .................................................194
Sketch: Temperature measurement using NTC...................................... 195
10.1.3 Analog joystick................................................................................198
10.1.4 Measuring light with a photodiode ........................................... 199
10.2 Digital measurements................................................................................................... 199
10.2.1 TL1838 or VS1838B infrared receiver......................................199
10.2.2 HC-SR04 ultrasonic distance sensor .........................................201
Syntax: pulseIn() function............................................................................. 202
Sketch: Ultrasonic distance measurement ............................................... 202
10.2.3 HC-SR501 motion sensor .............................................................205
Supplying power to the HC-SR501.............................................................. 207
10.2.4 The I²C interface.............................................................................207
SCL and SDA......................................................................................................... 207
I²C on the ATmega328 and 168................................................................... 209
Syntax: Including libraries............................................................................. 209
Syntax: I²C functions with Wire.h............................................................... 210
I²C sensors........................................................................................................... 211
Breakout boards................................................................................................... 211
10.2.5 BMP180 air pressure sensor....................................................... 211
Project: Pressure and altitude sensing with the BMP180 ................... 212
Syntax: Function definitions.......................................................................... 213
Sketch: Air-pressure sensor and altimeter .............................................. 214
Accuracy from double-oversampling............................................................... 219
10.2.6 MPU-6050 accelerometer.............................................................219
Sketch: Rotation and acceleration measurement.................................. 220
Output window...................................................................................................... 222

11

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 8 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

10.2.7 HMC5883L magnetic field sensor .............................................. 223


Project: 3D compass......................................................................................... 223
Sketch: 3D compass......................................................................................... 224
Output window...................................................................................................... 227
10.2.8 GY-87 multi-sensor......................................................................................... 227
Chapter 11 • Other components..................................................................................228
11.1 RF remote control .........................................................................................228
Coding................................................................................................................................ 229
Antenna............................................................................................................................. 229
11.2 Seven-segment displays.............................................................................230
Multiplexing...................................................................................................................... 230
11.2.1 Basic program for 1 to 6 digits ...................................................232
Sketch: 7-segment display with several digits ....................................... 233
int, float, hex, and degree displays ............................................................ 239
Syntax: modulo operator................................................................................ 239
Sketch: 7-segment display functions......................................................... 239
11.2.2 Project: Voltmeter.......................................................................... 244
Sketch: Voltmeter with 7-segment display ..............................................245
11.2.3 Project: Thermometer...................................................................246
Sketch: Thermometer with 7-segment display ......................................247
Thermostat............................................................................................................ 249
11.3 Text displays with back-lighting..............................................................250
Pinouts and functions................................................................................................... 251
Syntax: Controlling text displays ............................................................................ 252
Sketch: Example with user-defined characters ..................................................253
Text display with I²C interface..................................................................................... 254
11.4 Mini lasers....................................................................................................... 254
Laser application examples........................................................................................ 255
11.5 SD card module............................................................................................. 256
Connection to the Arduino.......................................................................................... 256
Syntax: SD.h file functions........................................................................................ 257
Sketch: Reading and writing files ............................................................................ 257
Chapter 12 • Rechargeable batteries and accessories .........................................260
Tip: Soldering round cells...................................................................................................... 261
12.1 Functionality and handling .......................................................................................... 262
12.2 Protection circuit............................................................................................................. 262
12.3 Connecting rechargeables in series ......................................................................... 263
12.4 Balancers............................................................................................................................ 263
12.5 USB charging regulators.............................................................................................. 264
Chapter 13 • Clever hacks and tricks.........................................................................265
13.1 Measuring battery level without components .....................................265

12

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 9 2022-07-27 20:20


Table of Contents

A weird measurement method................................................................................. 266


Sketch: Measuring battery voltage without components ............................... 266
Calibration......................................................................................................................... 268
13.2 Arduino in deep sleep..................................................................................268
Sketch: Sleep mode (bare template)..................................................................... 269
A pin to wake up............................................................................................................ 269
Sketch: Sleep mode (with wake-up pin).............................................................. 270
13.3 Low-battery switch-off...............................................................................271
Sketch: Low-battery switch-off................................................................................ 272
Integrating low-battery switch-off into projects ..................................................... 273
13.4 Pro Mini battery operation.........................................................................274
Reducing current consumption.................................................................................... 274
13.5 Project: Electronic die.................................................................................275
Syntax: EEPROM functions......................................................................................... 277
Sketch: Electronic die.................................................................................................. 278
Tip: LED effect board as a die.................................................................................. 285
Dice for cheaters............................................................................................................ 285
13.6 Analog measurement without waiting...................................................285
Sketch: Continuous analog measurement ........................................................... 286
Usage.................................................................................................................................. 289
13.7 Project: Universal remote control receiver..........................................291
Turning the principle on its head ............................................................................. 291
Sketch: 10-channel universal remote receiver .................................................. 294
Teaching the receiver..................................................................................................... 302
Tip: Exact clocking of the loop with one byte ..................................................... 304
Tip: Exact clocking of the loop using an integer ................................................ 305
Tip: Clocking of the loop with lateness options ................................................. 306
Tip: Clocking of the loop using only system time ............................................. 307
13.8 Project: Extreme altimeter........................................................................ 308
Sketch: Extreme altimeter......................................................................................... 308
Settings and possibilities............................................................................................... 312
Measuring small altitude changes........................................................................... 312
Weather trend barometer........................................................................................... 313
13.9 Project: Infrasound recorder....................................................................315
Sketch: Infrasound recorder..................................................................................... 318
Weather recorder........................................................................................................... 325
Index.....................................................................................................................................327

13

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 10 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Chapter 1 • ATmega boards


The ATmega328P from Atmel is one of the most popular
Microcontrollers for all applications that don’t require a
lot of computing power. RAM, ROM (i.e. EEPROM), as
well as processor and I/O connections are integrated in
a little chip, making up a standalone computer that can
be programmed using a USB cable or adapter connected
to a PC.
Fig. 1a: ATmega328P

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.

1.1 The Pro Mini form factor


The Pro Mini board is our choice for most of the projects in this book, as it’s available
from different manufacturers with only minor differences, and it’s very affordable. It’s
very small and has everything that such a board requires.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 11 2022-07-27 20:20


Chapter 1 • ATmega boards

A computer for a few euro

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.

Fig. 1.1a: Different Pro Mini boards

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 12 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

5 V / 16 MHz and 3.3 V / 8 MHz versions


The standard version has an ATmega328P microcontroller running on 5 volts at a
16 MHz clock frequency. If you only have a minimal source of power, you can opt for
the 3.3-volt version, which is only clocked at 8 MHz and thus runs half as fast. The
reason is that the microcontroller needs at least 4 volts to run at 16 MHz, and can even
run at up to 20 MHz on a 5 V supply. The microcontroller’s direct supply voltage is
normally labeled VCC.

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.

ATmega328P and ATmega168PA


Each voltage version also has two microcontroller versions – the ATmega328P and the
ATmega168PA. They’re identical, apart from the fact that the 168 has half as much
RAM, half as much program memory, and half as much EEPROM storage. For less
ambitious applications, this is more than adequate, and allows one to save a few cents
by using the ATmega168PA for most projects. Generally, however, we could also just
use the ATmega328P.

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.

GND: Supply voltage negative, i.e. ground.

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.

VCC: The microcontroller’s operating voltage – usually 3.3 or 5 volts.

0 – 13: The digital inputs and outputs. 0 and 1 also serve as serial TX and RX.

A0 – A7: Analog inputs. A0 to A5 can also be used as digital pins.

16

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 13 2022-07-27 20:20


Chapter 1 • ATmega boards

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 14 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.)

1.2 Uno versions


For the ATmega328P, there are also larger boards. The best-known is the Uno, and
there are many variations of it, such as the original Arduino Uno Rev3 and a plethora of
other Uno-compatible boards. Elektor even has its own Uno board, the UNO R4, that,
with its ATmega328PB variant of the chip, has additional timers and other features.

18

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 15 2022-07-27 20:20


Chapter 1 • ATmega boards

Fig. 1.2: Different Uno boards

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.

1.3 LilyPads and similar


These boards are optimized for low-
power, battery-operated devices,
especially those with lithium-ion bat-
teries. These boards save power, not
only through their 8 MHz clock fre-
quency, but also because they dis-
pense with the on-board voltage
regulator completely. Even a power
LED, which serves no purpose but to
indicate that the circuit is powered
(while itself consuming power), is
Fig. 1.3: The LilyPad
notably absent.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 16 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

1.4 The Nano board


The Nano is similarly compact to the Pro Mini, but has an integrated USB interface. A
CH340 chip is found underneath the board, enabling an interface between the serial
port (RS-232) and the USB connector.

The Nano offers one advantage over the


Pro Mini: The ATmega328’s analog refer-
ence input is available at the pinout,
labeled “AREF.” The Uno also has this
pin, while the Pro Mini and LilyPad
boards lack it.

Fig. 1.4: Nano board

The USB interface described in the next chapter is already integrated in the Nano board.

20

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 17 2022-07-27 20:20


Chapter 2 • USB adapter with serial interface

Chapter 2 • USB adapter with serial interface


In contrast with the Uno and Nano boards, the Pro Mini and LilyPad boards have no USB
connectors, so we need an appropriate adapter, which is only plugged in when we need
to upload our software.

Fig. 2: Two different USB-to-serial adapters

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.

2.1 USB adapters based on the CP2102

I would recommend the converter based on the


CP2102. It costs a few cents more, but experience
has shown that it works reliably, without driver
issues. It also works on any PC USB port. (Other
converters sometimes require changes to the port
settings when you plug them into another port.) The
CP1202 is identifiable by its square shape. Most of
the other commonly-used chips are more rectangular.

Fig. 2.1a: CP2102

21

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 18 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

2.1.1 Project: Universal serial adapter cable


Before we can begin with Arduino projects, we need a USB adapter. Important criteria:
A large, standard USB-A connector, CP2102 chip, 3.3 V and 5 V connection as well as
another connection pin, which is usually labeled “DTR.” (Of course we also need the
GND, TX, and RX pins). With some wires and a few small components, we can create an
optimal universal adapter cable.

Construction
We need:

• 1 USB-to-serial adapter (CP2102 chip)


• 2 meters black wire (appr. 0.14 mm2)
• 2 m red wire (appr. 0.14 mm2)
• 2 m yellow wire (appr. 0.14 mm2)
• 2 m green wire (appr. 0.14 mm2)
• 2 m blue wire (appr. 0.14 mm2)
• 1 pin header, 6-way (e.g. BL 1 6 G)
• 2 jumper cables (each 1-pin, female)
Fig. 2.1.1a: The necessary parts • 1 heat shrink tubing (2.4 / 1.2 mm)

22

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 19 2022-07-27 20:20


Chapter 2 • USB adapter with serial interface

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.

Fig. 2.1.1b: Adapter connections and wiring

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 20 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

 Tip: Neat soldering


After stripping, the threads should be twisted gently by finger, so that no strands are
sticking out. Then, the wires can be tinned with the soldering iron and a small drop
of fresh solder. Important: Always put the soldering iron at the soldering joint first.
Only then do you touch the solder to the soldering joint (or on the border between
soldering iron tip and solder joint), rather than to the soldering tip alone, as the
solder is supposed to melt and spread out at the solder joint rather than just
spreading all over the soldering tip. It’s completely wrong to first touch the solder to
the soldering iron and then introduce both to the area you wish to solder. Similarly,
contacts, pins or solder lugs are always tinned with a little fresh solder at first,
because only the flux within the solder causes it to spread along and adhere to the
metal surface. However, the flux evaporates quickly, so you need to use fresh solder
for each application, regardless of whether there’s solder hanging from the tip.
Rather tap or wipe the tip off occasionally. Most soldering stations have steel wool or
a sponge for the purpose – the latter should be moistened for the purpose.

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.

Ensure that the heat shrink tubing is far


enough away when soldering, so that it
doesn’t shrink right already. The rest of
the wires can be soldered to the corre-
sponding connector pins. The wire after
the black is the red, which is soldered to
Fig. 2.1.1d: Heat shrink shrunk
the “plus” connection, and then the
remaining three colors. When all the wires are soldered, the heat shrink sleeving is slid
down over the soldered connections, and shrunk firmly into place. For this, one may
hold each side of the connector assembly briefly over a cigarette lighter flame.

24

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 21 2022-07-27 20:20


Chapter 2 • USB adapter with serial interface

Now, we can braid the wires. To do this, the connector


must be fixed, preferably on a table edge, so that you
can tug gently on the wires. With 5 wires, there are
several ways of proceeding: I always grab the outer-left
and outer-right wire alternately and place them over
the two wires immediately adjacent to each of them,
toward the middle.

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.

When the entire cable is braided,


leaving only a few centimeters
remaining, fix the braid with a
suitable clamp, as in the image on the
right, so that nothing can come loose.
This can also work with an ordinary
clothes peg. Individual longer wires
can be trimmed a little at this point.
Fig. 2.1.1e Fig. 2.1.1f: Clamp

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!

Now, only the “plus” wire and the


DTR wire remain. These we will
make connectable. For this, we’ll
solder the ends of single-pin header
sockets from jumper wires. Best to
use jumper wires with the same
colors (so, red, and the color you
used for the DTR wire). We cut the
jumper wires about 3 cm from the
Fig. 2.1.1g: Soldered onto the header pins connector. Then, we strip about
3 mm of each wire, making sure
that we add heat shrink tubing to each wire (about 5 mm long), tin the wires and solder
them to each other – i.e. each of the remaining two wires gets a socket.

25

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 22 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Usage

Fig. 2.1.1h: Adapter cable, fully soldered

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).

2.1.2 Serial Micro USB adapter


This USB adapter is useful for when the USB
interface needs to be connected to the Pro Mini
board for long periods of time, for example in
applications that work with the PC and data is
output, or if the board must be supplied with
power using the USB connector. Connection to the
PC will then be done using an ordinary Micro USB Fig. 2.1.2 PC2102-based
cable. Micro USB adapter

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 23 2022-07-27 20:20


Chapter 3 • Buying tips

Chapter 3 • Buying tips


For microcontrollers and other components that we’ll need here, there are,
unfortunately no stores just around the corner that you can pop in to. Here, almost
everything is done via mail order. With online ordering, the world is open to us, with
enormous possibilities and opportunities, but also with some traps and pitfalls that we’ll
discuss here.

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.

3.1 Local suppliers and domestic mail order companies


In many countries, mail order companies were known to sell to their own country,
predominantly. Some of them are older than the internet. In the past, they sent out
catalogs of various thicknesses regularly, and people ordered by phone, or even by
postcard. That’s how it was done in the last century. Today, of course, all of the major
mail order companies have online stores.

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.

3.1.1 Conrad Electronic


Conrad is a German company that has been around for a
hundred years. It’s a very large supplier of electronic
components with the widest range of products in Germany
and meanwhile also internationally known. However, this diversity comes with a cost –
literally. Also, despite the large selection, some of the lower-priced components are
harder to find, while you can find others but at several times the price offered by other
retailers. Therefore, I cannot recommend Conrad Electronic, unfortunately.

27

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 24 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

3.1.2 Reichelt Elektronik


Reichelt is also a German supplier, which today also sells
internationally. In the offer are many standard electronic
components, stranded wires, consumables, etc. The
range of products is nowhere near as large as at Conrad, but the prices are usually
quite reasonable. There’s although no Pro Mini board to find at all. Some of the other
components that we need for the projects in the book can be found there.

3.1.3 Other online suppliers


There are several other suppliers in some countries, which I do not want to discuss in
detail here. Most of them also sell internationally, many also to private customers. The
prices often could be better, but it's also worth just browsing these sites. Therefore I
want to mention them briefly here:

Arrow Electronics   a US electronics distributor


TME   an electronics supplier based in Poland
Rutronik   another supplier from Germany
Pollin Electronic   only German & Austrian websites, but lot of cheap remaining stock
Lextronc   another supplier from France
Cotubex   a supplier from Belgium (side languages EN, FR and NL)

3.2 Big International online stores


With the spread of the internet, more and more online platforms have opened up that
enable you to place orders worldwide. Some serve exclusively as a platform for external
sellers, while others, such as Amazon, also offer a huge range of products themselves.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 25 2022-07-27 20:20


Chapter 3 • Buying tips

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 26 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

AliExpress shipping costs


Years ago, you could order many electronics parts from China on AliExpress for less
than one euro including shipping. That’s incredible when you consider that domestic
shipping alone (not including the value of the goods) often costs significantly more
in most countries. However, those times are over. While small, light goods still benefit
from moderate shipping costs, heavier goods (for example, speakers) are usually not
worth it.

Buyer Protection on AliExpress


As with the other online stores, AliExpress offers buyer protection to customers. A few
years ago, this was merely a bad joke. Even with clearly valid complaints, you’d be
required to provide photos, videos, etc., several times as evidence. As a buyer, you
often spent an hour of your time just to get 2 euro refunded.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 27 2022-07-27 20:20


Chapter 3 • Buying tips

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.

3.3 PayPal payment service


As already mentioned, PayPal acts as a trustee service
for online payments. The money is retained for a short
while so that it can be awarded to the favored party in
the event of a dispute. Thus, PayPal’s buyer protection is often the only safe payment
method available for online purchases. For online payments, I’ve been using PayPal
exclusively for many years.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 28 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

3.5 Caution: Pitfalls!


Even if you pay using PayPal, be aware that there are still some risks. If the merchant
tries to undermine the buyer protection, or cheats in a way that you don’t even notice
it, buyer protection won’t help.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 29 2022-07-27 20:20


Chapter 3 • Buying tips

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 30 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

3.6 Buying basic equipment


When ordering from China, whether via AliExpress, Amazon, or Ebay, always bear in
mind that delivery may take several weeks, so it’s a good idea to consider which
projects you want to tackle in the next few months, and what you’ll need for them.

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.

3.6.1 The necessary tools


The basic equipment includes a soldering station, or at least a simple soldering iron, but
it should not have more than 25 or 30 watts of power, or it will get too hot after some
time. In addition, we need good solder (1 mm Ø) with flux core. Unfortunately, the best
is the old type, consisting of almost 40% toxic lead, which should no longer be used
today and which is also hard to get.

Project: The simplest soldering station in the world


A low-power soldering iron is good for fine soldering work, but it won’t have enough
power for large solder joints. A high-power iron, on the other hand, is too hot and
coarse for fine work. The best choice is always a soldering station with adjustable
temperature.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 31 2022-07-27 20:20


Chapter 3 • Buying tips

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.

Fig. 3.6.1a: Switchable power-halving for soldering iron

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.

An ideal choice is a footswitch, as is used in freestanding lamps, or some other inline


switch, such as one used on an electric blanket. The switch housing also has enough
space to house the diode.

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.

Fig. 3.6.1b: 0 – I – II - Switchable power-halving for soldering iron

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 32 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Fig. 3.6.1c: Side cutter and wire stripper

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.

3.6.2 ATmega boards


If you’re not just doing test setups, but want to create microcontroller projects, it’s
good to have a few boards in stock. For starters, I recommend a few Pro Mini boards of
the 5 V / 16 MHz variant with the ATmega328 microcontroller, and a few LilyPads
running at 8 MHz, as these are optimally suited for battery-powered applications. One
or two Nano boards can also be purchased in case you need the analog reference port
for applications that communicate with the PC, and thus need a USB interface.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 33 2022-07-27 20:20


Chapter 3 • Buying tips

3.6.3 Power supply


If the board is not to be powered via USB, or the completed circuit requires a higher
voltage or current than can be supplied via USB, a power supply will be needed. On the
other hand, if the device needs to be mobile, batteries or rechargeables will be
required.

Mains power supplies


What kind of power supply is needed depends on what we want to do for each project.
To power only the Pro Mini board and perhaps one or two sensors or a few small LEDs,
neither a higher voltage (than 5 V) or current is needed. Even a 5 V / 100 mA supply is
sufficient.

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.

The thing about current and voltage


The power supply’s voltage must, of course, be in a range suitable for the components.
For the RAW connection on the Pro Mini 16 MHz variant, it’s 5 to 12 volts, while on the
8 MHz variant it’s 3.3 to 12 volts. For components such as motors, lamps, etc., the
nominal voltage may only be exceeded slightly at most. With a slightly lower voltage,
the performance usually decreases a bit. Sensors and other small components can
usually work on the VCC voltage generated by the board itself, when it’s supplied via
the RAW pin.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 34 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Widely used and somewhat more


expensive are power supplies such as
this, available in many different voltage
and current configurations. They’re also
usually of high quality. You should use a
power cable with a protective contact
(green-yellow wire), which is connected
to the middle of the 5 terminals, and
grounds the sheet metal enclosure. The
orange component next to the green
LED is a small trimmer potentiometer
for fine adjustment of the voltage (e.g.
between 11 and 13 volts for a 12 V
Fig. 3.6.3c: Power supply 12 V / 3 A power supply).

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 35 2022-07-27 20:20


Chapter 3 • Buying tips

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.

Disposable or rechargeable battery

Fig. 3.6.3e: 3 x AA and 3LR12 flat battery

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 36 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Fig. 3.6.3f: Different lithium-ion rechargeable batteries

Warning: Fake batteries


There are also 18650 fakes from China that basically work and are usable, but deliver
only a small fraction of the stated capacity – often below 1000 mAh. If you can manage
with that, you can buy them. However, I’d only recommend it at a price of less than 1
euro per piece.

The fake 18650s can often


be recognized by the fact
that capacities far in
excess of 3500 mAh are
advertised, which, even
with today’s state-of-the-
art manufacturing, is not
possible. Also, batteries
with labels such as “GTF,”
“GIF,” or, in former times
Fig. 3.6.3g: Different 18650 fake batteries
many “UltraFire” versions
are usually fakes. A look at the ratings and reviews can also be helpful. Sometimes
you’ll find that a buyer has tested the actual capacity and posted it. That's helpful, but
even such a test is no guarantee.

40

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 37 2022-07-27 20:20


Chapter 3 • Buying tips

Charge controller with protection circuit


This small board with a Micro-USB socket contains a
charge controller for lithium-ion batteries, as well as
an integrated battery protection circuit, all for under
1 euro. There are other versions with different USB
sockets. There are also simpler versions without the
battery protection, with a somewhat smaller board
and only a single IC. Fig. 3.6.3h: USB charger controller

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.

3.6.4 Standard components


Regardless of the individual topics and projects, there are certain standard components
that electronics hobbyists should always have on hand.

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.

The E12 series


For all of circuits in this book, we use only resistors with values from the E12 series, so-
called because every 12 steps up represents a tenfold increase in resistance.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 38 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

In a couple of examples in the book, a potentiometer of 10 kΩ is used. Best to have


one or two of these available. They’re often useful for setting analog values, e.g. to
control a motor’s speed.

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.

Film capacitors: Electrolytic capacitors:


• 10 nF 63 V • 10 µF 63 V
• 100 nF 50 V • 100 µF 35 V
• 1 µF 35 V • 1000 µF 16 V

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 39 2022-07-27 20:20


Chapter 3 • Buying tips

LEDs • LED 5 mm 20 mA red


As a simple indicator of • LED 5 mm 20 mA yellow
whether something is on • LED 5 mm 20 mA green
or off, as well as for • LED 5 mm 20 mA blue
gimmicks such as flashing
lights, running lights, etc., • LED 1 W red
you can use regular 5 mm • LED 1 W green
Fig. 3.6.4a: 5mm LEDs • LED 1 W blue
LEDs in different colors. If
you need it to be especially bright, and it is permitted to • LED 1 W warm white
consume a bit more power, 1-watt LEDs in various • LED 1 W cool white
colors are available cheaply from China.

The picture on the left shows a 1-watt LED with an aluminum


heat sink and connection plate. You can get the LEDs cheaper
alone, but these should be equipped with a mini heat sink or
should not be operated at full power. Identical form factor LEDs
are also available in 3-watt versions, where you have to pay
even more attention to sufficient cooling. These LEDs are
available in all colors, from infrared to ultraviolet, and even in
Fig. 3.6.4b: 1W LED unusual colors such as turquoise and in various shades of white.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 40 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

There are active and passive versions of this buzzer.


With the active version, we simply apply a DC voltage
for as long as we want it to beep. On the other hand,
the passive version requires us to supply the
necessary tone frequency. The active version makes
control very simple, and the buzzer can be louder,
Fig. 3.6.4c: Mini buzzers because it beeps at its resonance frequency.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 41 2022-07-27 20:20


Chapter 3 • Buying tips

3.6.5 Measuring tools


Apart from tools and components, a multimeter is also a basic part of every hobbyist
electronics engineer’s toolkit. Other measuring devices are useful, e.g. an infrared
thermometer and a digital Vernier caliper. These can all be obtained directly from China
for just a few euro.

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.

There are many different versions of the DT-830, some with


transistor tester, continuity beeper, thermometer, etc. Therefore,
there’s always an additional letter at the end of the model number
indicating the version. The picture shows the DT-830B as an
example. The “D” version is also widely used. The model numbers
sometimes exclude the hyphen, and some versions use the number
Fig. 3.6.5a: DT- 832 instead of 830.
830B multimeter

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.

A small design, similar in size to a


laser pointer, is totally adequate and
quite practical. There are larger
versions in pistol form that are
somewhat more expensive, but not
necessarily better. Fig. 3.6.5b: Infrared pen-type thermometer

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 42 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

To measure the temperature, hold the thermometer as close to the component as


possible. Black components measure very well, but light surfaces (e.g. an aluminum
heat sink) sometimes produce measurements lower than the actual temperature. To
alleviate this, it’s useful to use a permanent marker to make a black mark on the
component before measuring. Most of these thermometers perform the measurement
once the measurement pushbutton is released.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 43 2022-07-27 20:20


Chapter 4 • Optimal construction

Chapter 4 • Optimal construction


One question that every hobbyist asks at the outset of every project: What’s the best
way for me to build it? There are generally several methods, which I’ll introduce briefly,
each with its various advantages and disadvantages.

4.1 Construction on breadboard


For a test setup, plug-in boards, so-called
breadboards, are often used. They have many
rows of holes into which components, cables,
pins, etc. are inserted. Once inserted, the
components are in contact with the contact
row via the spring contacts underneath. In the
image on the right, the contact rows, which
are usually not easily visible externally, are
highlighted in gray. Here, on either side, you
can also see the long pairs of vertical rows
marked with red and blue lines. These are
usually used as plus (positive) and minus
(ground) connections. Additionally, on the
main part of the board are many short,
horizontal, mostly 5-hole rows, on which the
circuit is built.

These boards offer the simplest way to build


circuits, because you don’t have to solder
anything – everything is simply plugged in.
Nonetheless, I use these only reluctantly,
because circuits built on them have a few Fig. 4.1: Breadboard connections
disadvantages:

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 44 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

4.2 Point-to-point construction


This is how the first TVs and radio tubes were wired
in the past. The larger components (in the picture
on the right, for example, tube sockets, etc.) were
attached, and the remaining components were
simply soldered to them. To prevent short circuits,
longer, bare wires were covered with plastic, sili-
cone, or heat shrink tubing.

This construction method is also suitable for our


projects. On the small Pro Mini board, you can put
the pin headers on from the top, for example, and
then solder the pins to the bottom of the board so
that the long pins point up. You can then glue the
board onto a small wooden plate with some hot
glue, and wire the rest of the circuit onto it.

Fig. 4.2a: Point-to-point


construction

You have to consider which com-


ponents are likely to heat up
during operation. These are usu-
ally transistors, larger resistors,
and other power components. Of
course, hot glue doesn’t make
much sense for them, because it
goes soft again under heat. In
the example on the left, the
heat sink was affixed using heat-
resistant glue. On capacitors and
Fig. 4.2b: Point-to-point construction component leads, however,
of transistors and resistors there’s no significant heating.

The LilyPad is even better suited for


point-to-point wiring than the Pro Mini.
Its large connection surfaces are perfect
for soldering, especially if you’re still
inexperienced with a soldering iron.

Fig. 4.2c: Resistors on a LilyPad →

48

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 45 2022-07-27 20:20


Chapter 4 • Optimal construction

4.3 The thumbtack technique


An optimized form of point-to-point con-
struction is the use of thumbtacks, which
serve as a soldering surface on a plywood
board of at least 8 mm thick. We can place
thumbtacks on it for all connections, or
only sporadically to fix the circuit on place.

For a few projects here, there are construc-


tion diagrams that we can simply place
directly onto the board. The positions of the
tacks and components are indicated on
them, and the center of the tacks is
marked by a small cross, in each case. This
way, we can position everything exactly
and build the circuit directly on the con-
struction diagram. Ideally, this looks like
the picture on the right. Fig. 4.3: Construction on the diagram

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 46 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

However, some of the rows may have to be


cut in order to make several short strips out
Fig. 4.4b: Stripboard
of one continuous one. This is best done with
a cutter knife, which you use to carefully pry through the copper layer. Repeat this at a
distance of half a millimeter, so that a tiny piece of the copper layer crumbles out. This
way, the connection is safely severed. If you require additional connections, these are
done using wire bridges on the top side.

All in all, I think stripboard is more practical than hole matrix boards. However, con-
struction is not really easy.

Other grid arrangements


There are all kinds of special grid boards, e.g.
strip grids, where groups of only three solder-
ing points are connected in very short strips,
or even special arrangements, where an IC is
placed in the center and the connection lines
are all led to the outside with several holes
per connection strip, etc.

In principle, these special boards can be very


helpful. You only need find the right one for
your purpose, so that as few as possible addi- Fig. 4.4c: 3 x 1 grid
tional connections or cuts are necessary for the planned circuit. From China, for exam -
ple, you can also get very cheap mini boards, which are intended especially to accom-
modate smaller ICs, including for various SMD ICs, which we would not otherwise be
able to use at all. (SMD means “Surface-mounted device.” This are very small parts
which are soldered directly onto the circuit traces. For example, the ATmega328 and the
other parts on the Pro Mini board are SMD components.)

50

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 47 2022-07-27 20:20


Chapter 4 • Optimal construction

4.5 Construction on printed circuit board


By this, I mean a proper circuit board designed specifically for the circuit in question.
It’s obviously always better so simply insert the components and solder them on the
bottom side of the board. It saves a lot of effort if there’s a board designed for the
circuit.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 48 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

4.6 Pin header connectors


When I need to connect a multi-pole part (e.g. a motor or sensor), I like to use the type
of pin headers that come with the Pro Mini board, as well as their matching socket
headers. To both of these (male and female) connectors, you solder the relevant wires,
after you’ve added a bit of heat shrink tubing to them. Very important: Plug the male
and female connectors together before soldering, as described on page 23 to
prevent anything from getting deformed by the heat.

Fig. 4.6a: Male and female headers of various lengths

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 49 2022-07-27 20:20


Chapter 5 Programming

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.

5.1 The Arduino-platform


At arduino.cc/en/software, we can download and install the current free Arduino
software, or so-called “IDE” (Integrated Development Environment) for various
operating systems. At arduino.cc/reference/en, we can access reference detail about all
of the elements of the programming language, which is strongly based on C and C++.

Once installed, the Arduino IDE


looks like this on the PC. In the big
white pane, we write our Arduino
programs (known as “sketches”).
In the black pane at the bottom,
information is displayed.

If we click on the circular icon on


the top-left with the checkbox in it
(✓), the sketch is Verified (checked
for errors and compiled). The arrow
icon next to it (→) does the same,
but also Uploads the sketch to the
Arduino if the syntax is error-free.
The three remaining icons are for
creating a New sketch, and for
Fig. 5.1: Arduino IDE on the PC Opening and Saving, respectively.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 50 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

5.2 Our first program


As is customary, our first program will be to blink an LED. This is easy for Arduinos to
do, because almost every board already has an LED on it, assigned to an input/output
pin. So, for the blinking light, we need only a board, e.g. a Pro Mini, and no further
components.

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.

Syntax: setup() and loop() functions


The basic structure of every Arduino program are the setup() and loop() functions.

void setup() // This function is run once, at the start of execution.


{
[…] // The content of this function can consist of many lines.
}
void loop() // The loop() function is repeated continuously.
{
[…] // The double-slash causes the rest of the line to be ignored.
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 51 2022-07-27 20:20


Chapter 5 Programming

Sketch: Our first program 5_2.ino

void setup() // This function is executed only once at the beginning.


{
pinMode(13, OUTPUT); // pin 13, the LED pin, is set as output
}

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.)

void loop() // This function is repeated endlessly.


{
digitalWrite(13, HIGH); // LED on
delay(100); // wait 100 milliseconds
digitalWrite(13, LOW); // LED off
delay(100); // wait 100 millisecond
}

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:

Blink frequency = 1 / (0.1 s + 0.1 s) = 5 Hz

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 52 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

5.3 Uploading programs


To upload the sketch to our board (e.g. the Pro Mini), we need a serial adapter with a
USB connector, which we already know from Chapter 2. Furthermore, we have to
specify the correct board in the IDE. There are 4 versions of the Pro Mini (each with
either an ATmega168 or an ATmega328 and running at either 8 MHz or 16 MHz).

Fig. 5.3: Selecting the appropriate board version in the IDE

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.

Incidentally, with LilyPads, we have to be ensure that it really is an 8 MHz-compatible


version, as, from China you sometimes get 16 MHz versions much cheaper (even
though official 16 MHz LilyPads don’t exist), so you can’t select them in the IDE. For
these knock-off LilyPads, we need to select an “Arduino Pro or Pro Mini” board at
16 MHz. Otherwise, delays, timers, and serial connections will end up running at twice
the intended speed.

56

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 53 2022-07-27 20:20


Chapter 5 Programming

5.4 Downloading the programs


This Blink sketch, as well as all of the upcoming sketches, are available for free
download on the Elektor website at elektor.com/20243 which avoids tiresome and error-
prone typing. It’s best to extract these programs into the Arduino folder on the PC. In
Windows, you’ll find a folder named Arduino in the user’s own documents folder (e.g.
C:\Users\UserName\Documents\Arduino or similar). When extracting, the individual
folders (with the project names), must be extracted as well, not just the .ino files,
because they’ll only run if they’re located in a folder of the same name.

57

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 54 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Chapter 6 • Board inputs and outputs

The ATmega328 (and 168) has 20 pins,


each of which we can set as an input or
an output. They are numbered from 0
to 19, or alternatively, 0 to 13 and A0
to A5. Designations 14 and A0 are thus
equivalent. 6 of these inputs – A0 to A5
(resp. 14 to 19) – can also be read as
analog. Two further input pins, A6 and
A7, on the other hand, can only be
read as analog, but not switch or read
digitally.
Fig. 6: Pro Mini inputs and outputs

6.1 Reading inputs digitally


In the simplest case, one uses an input to register when a
button or switch is pressed. In the example on the right,
the voltage at the input is pulled to ground (0 volts) by the
resistor. The input itself is very high impedance, and
therefore hardly influences the voltage. If the input is read,
the result is "LOW” or 0.

If the switch or pushbutton is now activated, a HIGH level


of (almost) operating voltage VCC is present at the input,
which is then read accordingly as “HIGH” or 1. Fig. 6.1a: Switch to VCC

Usually, the switch is connected to ground and the resistor


to the positive pole (VCC), so the signal is inverted. When
it’s off, we get a “HIGH” signal, and a “LOW” when it’s on.

The 1 kΩ resistor is actually superfluous in both circuits. It


only serves as protection in case the input is accidentally
set as an output. This could cause a short circuit with the
switch and the Arduino would be in serious danger.
Nevertheless, this resistor is also often omitted, i.e.
replaced by a direct connection.
Fig. 6.1b: Switch to GND

58

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 55 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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.

But, we can omit the resistor to VCC, because the


ATmega328 (and of course the 168) already has such a
resistor internally at each input. You only have to activate
it via software, which can be done for each pin individually.
A free (unconnected) input is then no longer hanging
undefined in the air, but is pulled up to plus by the internal
pull-up resistor. So, for switching, we only need the switch
itself (and possibly the much smaller protection resistor).
Incidentally, this internal resistor is very imprecise and
can be between 20 and 50 kΩ, but this does not have to
concern us. Fig. 6.1c: Pull-up resistor

Of course, digital inputs can also


be controlled by other compo-
nents, e.g. via a sensor with a dig-
ital output. The picture also shows
an example with a transistor that
can switch the input to ground.
Here, too, the internal pull-up
resistor can be put to good use.
The incoming signal is inverted by
the transistor.
Fig. 6.1.d: Input examples

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 56 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Syntax: Variables
In principle, variables are simply storage areas that are allocated by giving them
a name.

byte a; // A variable of type “byte” is defined


byte a = 12; // This is how a variable is both defined and initialized with a value
a = 15; // This way a variable is assigned a value, after it has already been defined

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 57 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

Syntax: pinMode(), digitalRead() and digitalWrite()


The ATmega328 (and 168) has 20 pins (0 to 19) that we can switch as digital inputs
or outputs. Alternatively, pins 14 to 19 are also called A0 to A5.

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.”

A special form of this would be:

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 58 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Syntax: Comparisons and conditions

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.”

In this example, “n” is reset to 0 if “n” was already 7, otherwise it is incremented by


1. This way you can count the variable continuously from 0 to 7.

There are several comparison operators that we can use:

== 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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 59 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

Syntax: while and do-while loops

while (n > 10)


{
n --;
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 60 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Pushbutton switch 6_1.ino

byte led_pin = 13; // this pin is switched


byte input_pin = 2; // this pin serves as input (with internal pull-up)

bool state = LOW; // current switching state of the output

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

pinMode(led_pin, OUTPUT); // output


digitalWrite(led_pin, state); // switch LED (off when state is LOW)
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 61 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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.

6.2 Reading analog inputs


The ATmega328 (and 168) has not only digital inputs from which it can distinguish
"HIGH" and "LOW," but on 8 pins (A0 to A7) it also has analog-to-digital converters
(ADCs) that can measure the applied voltage with a resolution of 10 bits, corresponding
to a range from 0 to 1023.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 62 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Syntax: analogRead() and analogReference()

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.

The measuring range is from 0V (GND) to operating voltage (VCC) by default.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 63 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

6.2.1 Measuring voltages directly


In the simplest case, we apply the voltage to be measured directly to one of the analog
input pins. However, this must never be higher than VCC. If we accidentally apply
7 volts (or more) to the measurement input, the microcontroller may respond with
smoke signals.

So, if we can't guarantee that there is never more than VCC at


the measuring input, we need at least a protection circuit such as
in the picture on the right. Due to the series resistor, nothing
would happen if the voltage is higher, because the microcontroller
is internally protected so far. In normal measuring mode, the
resistor makes no perceptible difference, because the analog
inputs of the Arduino, at about 100 MΩ, have a 10,000 times
higher resistance. F. 6.2.1: Protection

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:

Syntax: Defining constants


#define measure_pin A0

This assigns constant “measure_pin” the value “A0.”

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 64 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Syntax: Serial transmission

Serial.begin(38400); // enable serial transmission

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.

Serial.println("Output with line break ");

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.”

Syntax: Calculations and assignments


The equal sign, “=,” assigns a value to a variable.
Example: “a = 3 * b;” triples the value in variable “b,” and stores the result in “a.”
The basic operations are +, -, *, and /.

To increment a variable by 1, you could type “a = a + 1;”


but there are some simplifications: “a++” does the same.
“a--” reduces the value by 1;

There are also +=, -=, *=, /= … and so on.


“a *= 3;” is a simple way to write “a = a * 3;” and so on.

68

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 65 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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.

Syntax: Rounding up and down

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:

b = floor(a); // this is always rounded down


b = ceil(a); // this is always rounded up

69

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 66 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Measuring voltages up to VCC 6_2_1.ino

#define measure_pin A0
#define vcc 5.00 // operating voltage in volts (calibrate!)

unsigned int amount = 10000; // number of measurements (max. 65535)


unsigned int counter = 0; // counts measurements
unsigned long measurements = 0; // summed measurements
float conversion_factor; // conversion factor to volt
float voltage; // voltage in volts

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

// output of voltage in mV and V


Serial.println(""); // blank line
Serial.print("Current voltage: ");
Serial.print(round(voltage * 1000)); // voltage in mV (integer rounded)
Serial.print(" mV resp. ");
Serial.print(voltage, 3); // voltage in volts (with 3 decimal places)
Serial.println(" V");

// Reset all for next counting:


counter = 0; // 0 measurements
measurements = 0; // nothing measured yet

70

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 67 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

}
}

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.

6.2.2 Measuring using internal ref. & voltage divider


To measure larger voltages (greater than VCC) we need a
voltage divider. The resulting measurement range can be
calculated from the two resistors and the reference voltage
used:
Umax = Uref * ((R1 / 10 kΩ) + 1)

"Umax" is the maximum measurable voltage and "U ref" is the


internal reference voltage. (Yes, I know that the inner
parentheses are of course superfluous, but I like to do it to Fig. 6.2.2: Measurement
using a voltage divider
avoid misunderstandings).

71

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 68 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

Measure range: R1: (E12-series) R1 can now be selected according to the required


measuring range. The table shows the required
up to 5 volts 47 kΩ (39k)
values, whereby the second resistor (to ground)
up to 6 volts 56 kΩ (47k) remains unchanged at 10 kΩ. Here, an internal
up to 8 volts 82 kΩ (68k) reference voltage of 1 V (instead of 1.1 V) was
used for the calculation, because the tolerance
up to 10 volts 100 kΩ range extends from about 1 to 1.2 V. Thus, it is
up to 15 volts 150 kΩ guaranteed in each case that the calculated
voltage can actually still be measured. In addition,
up to 20 volts 220 kΩ (180k)
R1 was rounded up in each case to the nearest
up to 30 volts 330 kΩ (270k) value of the common E12 range. For most ranges,
up to 50 volts 560 kΩ (470k) the maximum measurable value is actually still
somewhat higher than indicated in the table.
up to 80 volts 820 kΩ
Where the next lower value may be sufficient for
up to 100 volts 1 MΩ R1, this is also given in parentheses.

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:

Sketch: Measuring using internal ref. & voltage divider 6_2_2.ino

#define measure_pin A0 // measuring pin


#define gnd_resistor 10.00 // resistor to ground (in kiloohms)
#define r1_resistor 150.00 // R1 (in kiloohms)
#define reference 1100 // internal reference in mV (calibrate!)

unsigned int amount = 10000; // amount of measurements (max. 65535)


unsigned int counter = 0; // counts measurements
unsigned long measurements = 0; // summed single measurements
float conversion_factor; // conversion factor to millivolt
float voltage; // voltage in millivolts

72

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 69 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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

// serial output in mV and V


Serial.println(""); // blank line
Serial.print("Current voltage: ");
Serial.print(round(voltage)); // voltage in mV (integer rounded)
Serial.print(" mV resp. ");
Serial.print(voltage / 1000, 3); // voltage in volts (with 3 decimal places)
Serial.println(" V");

// Reset all for next counting:


counter = 0; // 0 measurements
measurements = 0; // nothing measured yet
}
}

In the loop, the counter is incremented by 1 each time, an analog measurement is


performed, and the result is added to the variable “measurements.” Only when the
counter has reached “amount” is the voltage calculated, output, the old value is
deleted and the counter is reset.

73

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 70 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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:

reference = reference_old * actual_voltage / measured_voltage

“reference_old” is the previously-entered reference voltage (1100). “actual_voltage” is


the voltage we know or measure using an accurate multimeter, and we see the
measured voltage in Serial Monitor. Then we just have to enter the new (corrected)
value for the reference voltage in the sketch, and of course reload it to the Arduino. The
value should be between 1000 and 1200, otherwise something went wrong.

Low-tolerance resistors (±1%) are especially important if several different


measurements (at several analog inputs) are to be performed using one circuit. Thus,
all measurements are accurate if we have determined and specified the internal
reference voltage exactly. If, on the other hand, only one voltage is measured, the
resistors’ tolerances may be a bit higher. Then, we get a slightly different reference
voltage during calibration, but the measurement is still correct as long as the voltage
divider was used during calibration.

6.2.3 Measuring directly using the internal reference


If we have to measure only very
small voltages of up to about
1 volt, we don’t need the voltage
divider, but can apply the signal
directly to the input. A 100 nF
capacitor and a 10 kΩ resistor – as
in the picture – can be useful for
protection and smoothing.

Fig. 6.2.3: Measuring voltages directly


74

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 71 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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:

conversion_factor *= 1 + (r1_resistor / gnd_resistor); // Voltage divider

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.

Tip: Commenting out lines


When programming, you often make quick changes and try out many things. Most of
the time it makes sense to know afterwards what you have changed and what it
looked like before. Instead of deleting lines that you might need again later, it is
better to comment them out:

// conversion_factor *=1 + (r1_resistor / gnd_resistor); // Voltage divider

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.

reference = reference_old * actual_voltage / measured_voltage

75

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 72 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

6.2.4 Measuring current


To measure current, we need a “Shunt.” This is the name for an extremely low resis-
tance (often much smaller than 1 ohm), which is connected in series in a circuit. It is
only used to determine the current based on the (very low) voltage that drops across it,
using the simple formula: I = U / R.

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.

The picture on the right shows such a measuring circuit with a


1 ohm shunt (in the lower part). The upper part shows
(symbolically) a load, which may have its own circuit (e.g. running
on 12 V), but both have a common ground. Only the load’s positive
side is connected directly to its voltage source. The negative pole of
the load goes via the shunt (1 Ω) to the common ground. In this
way, the current can be measured on the basis of the voltage drop
at the shunt. In this case (at 1 Ω), the measuring range goes up to
1 A, because at 1 A, exactly 1 V drops at 1 Ω, and that’s as far as
we can measure using the internal reference (1.1 V ±10%). The
Fig. 6.2.4:
load can be anything, e.g. a 12 V power LED. Current
measurement
For this, here’s another short sketch:

Sketch: Measuring current 6_2_4.ino

#define current_pin A0
#define resistor 1.00 // measuring resistor (shunt)
#define reference 1100 // internal reference in mV (calibrate!)

unsigned int amount = 10000; // amount of measurements (max. 65535)


unsigned int counter = 0; // counts measurements
unsigned long measurements = 0; // summed single measurements
float conversion_factor; // conversion factor to milliampere
float current; // current in milliamperes

76

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 73 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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

// serial output in mA and A


Serial.println(""); // blank line
Serial.print("Actual current: ");
Serial.print(current); // current in milliamperes
Serial.print(" mA resp. ");
Serial.print(current / 1000, 3); // current in amperes (with 3 decimal places)
Serial.println(" A");

// Reset all for next counting:


counter = 0; // 0 measurements
measurements = 0; // nothing measured yet
}
}

In loop(), as with voltage measurement, the counter is incremented by 1 each time, an


analog measurement is performed and the result is added to the variable
“measurements.” Only when the counter reaches “amount” is the current calculated,
output, the old value is deleted and the counter is reset.

77

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 74 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

The measurable range with a measuring resistor of 1 Ω is up to 1 A. But, be careful:


The resistor must also cope with the power loss. With 1 A at 1 Ω this is exactly 1 W.
But, I recommend using a 2 W resistor at 800 mA upward, to be on the safe side. Such
a resistor is a little longer and thicker and therefore does not get quite so hot.

Possible ranges
(Almost any) other measuring range is possible. The table is a guide:

In this way, we can set up measuring


Current Resistor (shunt)
ranges with a maximum current of 1 mA
up to 1 mA 1 kΩ / ¼ watt up to about 4 A, whereby the power loss
in the resistor increases with increasing
up to 2 mA 470 Ω / ¼ watt
current. At the lower values in the table,
up to 5 mA 180 Ω / ¼ watt which are marked with asterisk *, the full
up to 10 mA 100 Ω / ¼ watt measuring range up to a 1 V voltage
drop is no longer used, but only a part
up to 20 mA 47 Ω / ¼ watt
thereof. This limits the power dissipation,
up to 50 mA 18 Ω / ¼ watt so that a 2-watt resistor is sufficient.
up to 100 mA 10 Ω / ¼ watt
A disadvantage is the somewhat lower
up to 200 mA 4.7 Ω / ½ watt resolution of the measurement results.
up to 500 mA 1.8 Ω / 1 watt The 10-bit value of the analog-to-digital
converter normally ranges from 0 to
up to *800mA (1 A) 1 Ω / 1 watt 1023, and for the final value, for exam-
up to 1 A 1 Ω / 2 watt ple, we only use about 40 percent of the
range, i.e. from 0 to 400.
up to *1 A (*1.4 A) 0.47 Ω / 1 watt
up to *1.6 A (2 A) 0.47 Ω / 2 watt With the current values in parentheses,
the maximum permissible power dissipa-
up to *2 A (*2.4 A) 0.33 Ω / 2 watt
tion of the respective resistor is almost
up to *2.5 A (*3 A) 0.22 Ω / 2 watt reached. It’s better if we don’t go
up to *3.1 A (*3.6 A) 0.15 Ω / 2 watt completely to the power limit and rather
limit ourselves to the values before the
up to *3.8 A (*4.4 A) 0.1 Ω / 2 watt
parentheses.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 75 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

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.

reference = reference_old * actual_current / measured_current

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.

reference = reference_old * actual_voltage / R / measured_current

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 76 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

6.2.5 Resistor measurement


Resistors are best measured with a voltage divider consisting of
a known resistor (R1 in the picture) and the resistor under
measurement (R?). The positions of the two resistors may also
be interchanged, although this changes the calculation formula.

The previous disadvantage of VCC as a measurement reference


now turns out to be an advantage, because, if VCC is not
stable, both the reference voltage and voltage measured
change. For this measurement we are no longer concerned with
the measured voltage as an absolute value, but only with the
percentage of VCC, which is independent of total VCC, and Fig. 6.2.5a:
solely dependent on the ratio of the resistors. Measuring resistance

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.

Many simple sensors are also often


implemented by using resistance
measurement. The picture on the right
shows two common examples – bright-
ness measurement using a light-
dependent resistor (LDR) and tempera-
ture measurement using a thermistor
(NTC).

Here too, the most accurate result is


obtained if the two resistances are of
similar size. If you want to measure
the room temperature at home, for Fig. 6.2.5b: Brightness and
example, and the NTC used has a temperature measurement
resistance of 47 kΩ at 25 °C, then the
same value is recommended for the
fixed resistor.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 77 2022-07-27 20:20


Chapter 6 • Board inputs and outputs

Sketch: Resistance measurement 6_2_5.ino


#define measure_pin A0
unsigned int amount = 10000; // amount of measurements (max. 65535)
unsigned int counter = 0; // counts measurements
unsigned long sum = 0; // summed single measurements
float r1 = 10000; // resistance R1 (example 10 kiloohms)
float ratio; // resistor ratio
float rm; // measured resistor
void setup()
{
pinMode(measure_pin, INPUT); // measuring pin as input
Serial.begin(38400); // enable serial transmission
}
void loop()
{
counter++; // count
sum += analogRead(measure_pin); // add new measurement
if (counter == amount) // if amount is reached
{
ratio = sum / float((1024 * (long)amount) - sum); // resistor ratio
rm = r1 * ratio; // calculate resistance value from ratio

Serial.println(""); // blank line


Serial.print("Resistor: "); // output follows in ohms, kiloohms or megaohms
if (rm >= 1000000) // if at least 1 megaohm
{
Serial.print(rm / 1000000); // display in megaohms
Serial.println(" MΩ");
}
else if (rm >= 1000) // if at least 1 kiloohm
{
Serial.print(rm / 1000); // display in kiloohms
Serial.println(" kΩ");
}
else // if under 1 kiloohm
{
Serial.print(rm); // display in ohms
Serial.println(" Ω");
}
counter = 0; // reset counter
sum = 0; // reset sum
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 78 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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:

rm = r1 / ratio; // calculate resistance value from ratio

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.

6.3 Switching outputs digitally


Similar to reading levels “HIGH” or “LOW” digitally at the input/output pins, we can also
switch each pin as an output and send out a corresponding signal. We have already
done this on pages 55 and 64 by switching the on-board LED at digital Pin 13 on and off
using “digitalWrite().” The exact syntax is explained on page 61.

“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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 79 2022-07-27 20:20


Chapter 7 • How do you switch something?

Chapter 7 • How do you switch something?


If we want to switch more than just the mini LED on the Arduino board, we need to
know what voltages and currents we’re dealing with. Some can be switched directly,
while some need a resistor in series. For higher voltages and currents, more resistance
is needed.

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.

An LED’s current-voltage curve is not


linear. The figure shows the characteristic
curves of common LEDs and that of a
100 Ω resistor (gray line) in comparison.
As we can see, at a low voltage (e.g.
1 V), almost no current flows through the
LEDs (in contrast to the linear resistor).
At 4 or 5 V, however, a huge current
would flow and destroy the LEDs as well
as the Arduino. The solution is a simple
resistor, which we connect in series with
the LED. Such a resistor, with the purpose
of limiting the current to be able to
operate a component using a higher
voltage, is called a series resistor.
Fig. 7.1: LED characteristic curve U(V)

83

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 80 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

7.1.1 Calculating the series resistance

The calculation of series resistance is quite


simple. We only need to know three values,
the voltage that is applied in total (to the LED
and the series resistor), usually 5 volts, the
voltage that should be applied to the load (i.e.
the LED), and the corresponding current that
flows through the LED at the desired voltage.
The voltage at the resistor is the difference,
i.e. total voltage minus LED voltage. The
current in the LED and the resistor is the
same. Thus we know the current and voltage
Fig. 7.1.1: LEDs with series resistors in the resistor and can now calculate it:

R = U/I = (Utotal – ULED) / ILED = (5 V – 2 V) / 0.013 A = 230.8 Ω

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.

7.1.2 LEDs in battery operation


For applications in battery mode with lithium-ion battery we have an operating voltage
of about 3 to 4.2 V instead of the 5 V. Here, we can use a series resistor of 150 or
100 Ω for red, yellow, and green LEDs. But we can also save power and stay at 220 Ω
(as with the 5-volt operation).

84

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 81 2022-07-27 20:20


Chapter 7 • How do you switch something?

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.

7.1.3 Switching direction to ground or positive


The picture on the right shows another way to switch LEDs
and other small loads with an Arduino board. Here, the
LED is not (as before) tied to ground, but rather to
positive. So, the output now works the other way round:
When you switch it to HIGH (VCC), the LED is off. Both
sides of the LED are thus connected to the same voltage
potential. If you switch it to LOW (or 0), the LED lights up.

Fig. 7.1.3a: LED to VCC →

Now you could of course combine the two types of


switching, as in the picture on the left. This way, you could
make an alternating flasher using two LEDs on only one
output. Depending on the switching state, one or the other
LED is always lit.

← Fig. 7.1.3.b: Using 2 LEDs

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 82 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

7.1.4 Project: LED effect board


After so much theory we now come to the first practical Arduino project. The LED effect
board is a simple setup with up to 20 LEDs arranged in a circle. But you can also start
smaller by equipping only every second or every fourth LED, or only an arc of the circle.
We can then control the LEDs as we like – an ideal playground for programming
practice. There are also a few ready-made effect programs.

For the first assembly suggestion, I


have created a construction plan with
which you can easily build the circuit
using some thumbtacks on a small
board of soft wood. The thickness
should be at least 8 mm, so that the
thumbtacks don’t poke through the bot-
tom. You can download the schematic
from the Elektor website at elektor.-
com/20243. If necessary, you can also
scan the plan for the LilyPad from
page 88 of the book and print it out. On
page 89 there is also the Pro Mini ver-
sion. In any case, the print settings are
important, because the printout must
Fig. 7.1.4a: Construction using thumbtacks be exactly in the original size.

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:

• 1 LilyPad or Pro Mini Board


• 5 to 20 LEDs (see text)
• 5 to 20 resistors (see text)

Depending on construction:

• 1 wooden board Ø13.5 cm, 1 cm thick


• 1 printed construction diagram
• 10 to 40 thumbtacks (2 per LED)
Fig. 7.1.4b: Required parts

86

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 83 2022-07-27 20:20


Chapter 7 • How do you switch something?

For the setup in the picture on the


right, we use 1-watt LEDs. These are
actually completely oversized here,
because we only run them at about
30 mW, but they don’t cost much
more than standard LEDs and have
the perfectly fit for connectors. Of
course, you can also use normal
5 mm LEDs (as in the components
picture on the previous page). The
component leads are then bent
outward at right angles (all at the
same distance from the LED body)
and then cut to fit the solder pads.
Fig. 7.1.4c: Circuit ready soldered

Fig. 7.1.4d: Circuit diagram

Determining resistor vales


The required series resistors depend mainly on two factors – firstly, the operating
voltage used, and secondly, whether blue or white LEDs (as opposed to red, yellow, and
green) are used, as the blue and white have a slightly higher operating voltage.

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:

Red, yellow, or green LEDs White or blue LEDs

3 to 4.2 V operating voltage 220 Ω 120 Ω

5 V operating voltage 330 Ω 220 Ω

87

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 84 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

LilyPad construction template

Fig. 7.1.4e: LilyPad construction template

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 85 2022-07-27 20:20


Chapter 7 • How do you switch something?

Pro Mini construction template

Fig. 7.1.4f: Pro Mini construction template

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 86 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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];

This creates an array of 20 boolean variables, from led[0] to led[19]. When


programming, you always start counting with 0, so it’s not 1 to 20 but rather 0 to 19.
When addressing variables later, their indices can also be variables themselves.
Example: led[n]. Thus, the variable “n” indicates which indexed member of the led[]
array is needed.

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.

Syntax: for loop


for loops are usually used when you want to repeat statements several times. A
variable (in the following example, “n”) serves as a counter.

for (byte n = 0; n < 20; n++)


{
led[n] = HIGH;
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 87 2022-07-27 20:20


Chapter 7 • How do you switch something?

Syntax: delay() and system time

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):

millis() returns the elapsed time in milliseconds, in unsigned long format.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 88 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Simple LED effect


This program changes the state of the LEDs (on/off) continuously one after the other.
First, all LEDs go on one after the other, then off one after the other… and so on. If the
effect runs very fast, it looks like a continuous rotation movement, similar to some
waiting icons seen on a PC screen.

Let's look at the sketch:

Sketch: LED rotation effect 7_1_4_a.ino

// Settings:

byte amount = 20; // total number of channels resp. LEDs (3 to 20)


bool active = LOW; // outputs high- or low-active
byte pin[20] = {2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,0,1}; // pin order
int blink_time = 50; // time in milliseconds each time until the next change

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.

bool led[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // current states of LEDs


byte n; // count variable

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 89 2022-07-27 20:20


Chapter 7 • How do you switch something?

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 90 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Your own versions


If you like, you can also play around by changing the settings at the beginning, tinker
with the program code, or even create your own programs.

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.

Syntax: Random values with random()


With random(min, max) you can easily generate integer random numbers from “min,”
inclusive up to “max,” exclusive.

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.

Here’s another program for the LED board:

LED running light effects


With this program we can define an arbitrary LED light pattern in advance, and let this
pattern run in a circle with at an arbitrary speed. This sounds similar to the previous
program, but is quite different. While previously only the position where the LEDs are
currently switched rotated, here the entire light pattern rotates.

94

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 91 2022-07-27 20:20


Chapter 7 • How do you switch something?

Sketch: LED running light effects 7_1_4_b.ino

// Settings:

byte amount = 20; // total number of channels resp. LEDs (3 to 20)


bool active = LOW; // outputs high- or low-active
byte pin[20] = {2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,0,1}; // pin order
bool led[20] = {1,0,0,0,0,1,1,1,0,0,0,0,1,1,0,0,0,0,1,1}; // blink pattern
char movement = 1; // number of positions, the pattern moves at each pass
int blink_time = 200; // time in milliseconds each time until the next change

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.

byte led_new[20]; // new blink pattern


byte n; // count variable
char pos; // LED position

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 92 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 93 2022-07-27 20:20


Chapter 7 • How do you switch something?

Other Blink applications


This program and the Blink circuit can be used for many other applications. It does not
always have to be 20 LEDs, and the arrangement does not have to be circular.

For example, these are flashing


name tags with 6 LEDs each, which
were once made for a TV show and
used there.

Fig. 7.1.4g: Name badges with effects

Here, the running light program was used,


this time with the simpler flashing pattern, Fig. 7.1.4h: Wiring
{1, 0, 1, 0, 0, 0}.

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:

7.1.5 Battery protection for effect flasher


Lithium-ion batteries do not like to be discharged too deeply. You should not discharge
them below 2.8 V (3 V to be on the safe side). In section 13.3 on page 271, there’s the
“Low-battery switch-off” extension that allows us to optimize all program sketches for
battery operation. In this way, the battery voltage is measured regularly. When the
battery is empty, the Arduino is put into deep sleep and consumes almost no power. If
the battery is charged again, you can restart the effect flasher by resetting it (use the
reset button or disconnect the battery briefly).

97

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 94 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

7.1.6 LEDs with integrated series resistor


So far, we’ve only discussed ordinary standard LEDs with a rated current far below
100 mA and voltages (depending on the color) of between 1.8 and 3.6 V. Often, you can
find special versions of them that already have the series resistor integrated, and which
are then specified for a direct operating voltage of 5 or 12 V. Of course, you can also
use these LEDs, and save on the series resistor. I still advise against it, because you
lose some flexibility, for example in adjusting the brightness, or, for example, if you
build a battery-operated device (max. 4.2 V), you can then of course not use 12-volt
LEDs. You can’t tell from the LEDs which voltage they require, so there’s also the risk of
confusion.

7.1.7 Power LEDs


Now, of course, there are many types of power LEDs. The rules for calculating the series
resistor are the same. Some power LEDs already contain several individual LEDs in
series internally and can thus be operated at a higher voltage without a series resistor.
But they all have one thing in common: They cannot be powered directly by an Arduino,
and need a transistor as a switch.

Fig. 7.1.7: Various Power LEDs of 1 W, 3 W, 10 W, 20 W, and 50 W

98

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 95 2022-07-27 20:20


Chapter 7 • How do you switch something?

7.2 Switching using a transistor


With a transistor, we can switch larger currents as
well as larger voltages than with an Arduino output
directly. Bipolar (conventional) transistors have three
terminals, namely base, emitter, and collector, abbre-
viated as B, E, and C. NPN transistors are common.
The emitter is usually connected to ground (minus).
The transistor is controlled at its base, whereby the
base-emitter path has the characteristic curve of a
silicon diode.

Fig. 7.2a: Transistor as switch

You can see clearly from the characteris-


tic curve that the current increases more
and more steeply from 0.7 volts upward.
Therefore, the resistor between the
Arduino output and the transistor is
absolutely necessary. This resistor deter-
mines the base current that flows into
the transistor and with which the output
of the Arduino is also loaded. A much
higher current can then flow through the
Fig. 7.2b: Diode characteristic curve U(V) collector terminal to the emitter.

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.

Sometimes you see circuits where a resistor is


connected between base and emitter, i.e. to
ground, as shown here. This is not really necessary.
It only ensures that the base-emitter voltage goes
back to zero (and stays there) when the output of
the Arduino is set as an input and thus deactivated.
Then, due to tiny leakage currents and noise that
the transistor amplifies many times, you could
otherwise end up with a very small current flowing
through the load. But, this does not happen when
the output is active and LOW. Fig. 7.2c: With B-E resistor

99

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 96 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Fig. 7.2d: Different transistors (BC547C, BC337-40, 2  NTD4906N, BD435)

7.2.1 BC547 transistor


Type Gain Max. U Max. I
The BC547 is a widely used, inexpensive
A 110-220
universal transistor for currents of up to
BC546 B 200-450 65 V 100 mA
100 mA. That’s not much, but it’s 4 times
what an Arduino can do directly. It also has C 420-800
some relatives, as the table on the right A 110-220
shows. The differences are in the maximum BC547 B 200-450 45 V 100 mA
voltage we can switch. There is also often a C 420-800
further division into A, B, and C types, A 110-220
selected by gain. However, for higher
BC548 B 200-450 30 V 100 mA
currents of 50 or 100 mA, the amplification
C 420-800
factor is much smaller than specified.
Nevertheless, we can use any of these
transistors for our purposes.

The circuit diagram now shows an actual


use case, where RL stands for any DC load.
This can be, for example, a small motor or
several LEDs. V2 is the DC voltage that
supplies the load (max. 45 V for the
BC547). The transistor is controlled via the
470 Ω resistor with about 8 mA. Of course,
this does not mean that 800 mA will now
flow through RL at the amplification factor
Fig. 7.2.1:
of 100. The actual current depends on load Switching using a BC547
RL and its voltage, V2. The base current calculated
from the amplification factor must always be significantly higher (preferably 3 times
or more) to ensure that the transistor conducts perfectly and only a small residual
voltage remains between the collector and emitter. The dotted resistor can be omitted
if the pin from the board is always set as an output.

100

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 97 2022-07-27 20:20


Chapter 7 • How do you switch something?

7.2.2 BC337-40 transistor


The BC337 is a small NPN transistor that does quite a lot for little money. you
can get it for a measly few cents. We can use it to switch up to 45 V with a
current of up to 800 mA, which is not only much more than the 25 mA / 5 V
that an Arduino output can deliver directly, but also multiple times that of the
BC547. In addition it can replace the BC547 in every respect.

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.

The transistor is controlled here via the 180 Ω


resistor, with about a 4-volt drop from a HIGH
signal. The resulting current of about 22 mA can be
handled by the Arduino without any problems, and
the transistor can switch the maximum permissible
load current of 800 mA. If less current is needed
(below 500 mA), you can increase the resistor to
220 Ω or higher. This takes the load off the Arduino.
The dotted resistor can also be omitted here if the
Fig. 7.2.2:
Arduino connection always remains set to output.
Switching with the BC337

7.2.3 BD435 transistor


The BD435 can switch up to 4 amps and even has an extremely low
BD435
saturation voltage. (This is the remaining voltage between collector
max. 32 V
and emitter in the switched-on state). You can use the BD435 in
max. 4 A
exactly the same way as the BC337, but then you can only switch 1 A
with it.

101

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 98 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

To use the maximum of 4 amps, we need


a second transistor, as shown in the
image on the right. The BC547 (A, B, or
C) is sufficient, as is the BC337. Here,
this transistor is operated as a collector
circuit, and it works like this: If the
Arduino supplies HIGH potential at the
output, the voltage at the emitter also
rises (measured against ground), but not
quite up to the HIGH potential (because
then the transistor would have no base- Fig. 7.2.3: For loads up to 4A
emitter voltage at all and would therefore be blocking) but only up to about 0.7 V
below the HIGH potential, i.e. a good 4 V at the emitter. This voltage, however, is
easily enough to drive the BD435 now via the 47 Ω resistor with about 70 mA, far
more than the Arduino could supply directly. The Arduino is loaded with less than
1 mA by the additional transistor.

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.

Tip: Heat test


By the way, there is a simple test to determine whether a power transistor needs a
heat sink. To do this, put a drop of water on the transistor from time to time. If the
transistor becomes so hot that the drop evaporates in a few seconds, or even boils,
switch off quickly and add the heat sink!

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.

7.2.4 Switching using MOSFETs


N-channel MOSFETs are controlled in a similar way
to bipolar NPN transistors, but some things are quite
different. The previously-mentioned connections,
base, collector, and emitter, are now called gate,
drain, and source. The main difference is that no
current flows through the gate terminal. The applied
voltage alone is sufficient to switch the transistor.
The resistor before the gate doesn’t do much, as
you could also connect the Arduino directly, but the
resistor is generally used for safety. Fig. 7.2.4a:
Switching with a MOSFET

102

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 99 2022-07-27 20:20


Chapter 7 • How do you switch something?

The NTD4906N and IRLR8726PbF


Actually MOSFETs are rather unsuitable for Arduino
circuits, because they usually need more than 5 V
for control. Fortunately, there are special types that
switch well from 4.5 V, such as the NTD4906N and
the IRLR8726PbF. At local mail order companies you
often won’t find these transistors, but at AliExpress
you can get them in packs of hundreds for a few
cents each. For smaller quantities, you can also
search on Ebay.
Fig. 7.2.4b: The NTD4906N

Fig. 7.2.4c: NTD4906N, freely wired, and on a PCB →

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.

Fig. 7.2.4d: Pinout and two other package configurations

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.

NTD4906N According to the datasheet, the NTD4906N can switch up to 54 amps


max. 30 V and the IRLR8726PbF even more, but we shouldn't take these numbers
max. 10 A too seriously, because we can’t cool the transistor that well. However,
we can switch up to 10 A with it easily, if it is soldered to a board with
large solder pads (or similar). You can also put a small heat sink on it after installation,
using heat conductive glue.

103

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 100 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Tip: Thermally conductive adhesive


From China, you can get cheap small tubes with a silicone-like mass under the names
“Heatsink Plaster” and “HC 910.” These adhesives are very suitable for gluing mini
heat sinks onto ICs or power transistors. You can also use them to glue large power
LEDs (10 W – 100 W) to an aluminum plate or a heat sink. The glue can also be used
to affix other components, especially for components that heat up during operation,
where hot glue would not be suitable.

7.2.5 ULN2003A transistor array


In order to switch several larger loads separately,
instead of individual transistors, we can also use
a special IC that already contains several such
transistor circuits internally. The ULN2003A
contains seven copies of the circuit pictured on
the right and can switch up to seven loads with
up to 500 mA each and a maximum of 50 V.

Fig. 7.2.5a: Internal circuit

The two transistors are operated here as a


Darlington circuit. This increases the amplification
factor enormously, so that only a small current is
needed to drive it. The disadvantage is a relatively
high saturation voltage, which remains between
output and ground in the switched-on state. This can
be as high as 1 V at a load current of 100 mA, and
around 1.5 V at 500 mA.

The picture on the left shows the pinout of the IC.


The ground pin (GND) is, of course, connected to
ground. Then, we only have to connect the inputs to
Arduino outputs and can then switch the individual
loads on the IC’s outputs, which can be connected to
a positive voltage of up to 50 V.

Fig. 7.2.5b : Pinout

104

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 101 2022-07-27 20:20


Chapter 7 • How do you switch something?

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.

By the way, with a simple trick,


we can switch currents far above
500 mA using this IC. All we
have to do is connect several of
the transistor stages together.
Since the inputs and outputs on
the IC are all next to each other,
this is very easy: Just tie a few
input pins together and connect
the corresponding output pins on
the other side.

The example on the right shows


an RGB LED controller with two
interconnected switching stages
per color. In this way, a current
of up to 1 A per color is possible. Fig. 7.2.5c: Example, each 2 connected together

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.

7.2.6 ULN2803A transistor array


This is the 8-channel version of the ULN2003A, which I don’t want to leave
unmentioned. This IC is simply longer by one pin per side and thus includes an
additional switching stage. Everything else is the same. Ground and COM are still on the
very outer pins, even though their pin number has increased by 1 due to the additional
pair of pins.

105

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 102 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

7.3 Switching using a relay


A relay is a mechanical switch whose switching
contact is moved by an electromagnet. The circuit
diagram shows how to use a relay. The
rectangular box with the diagonal line indicates
the coil. The dashed line represents the
mechanical connection to the switch. Here you
can see some advantages and disadvantages of
relays. On the one hand you usually need the
additional transistor, because most relays need a
bit more current than an Arduino output can
Fig. 7.3a: Switching with a relay
deliver directly.

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.

Thus, a relay can also contain several switches, for


example, in order to switch several separate circuits
simultaneously. Often, they are also changeover
switches, as in this example. One could switch off one
load and switch on another at the same time in one
switching operation.

The biggest disadvantage of relays, however, is their


rather high susceptibility to wear, especially when
switching higher currents. I therefore always
recommend, when using relays with several switches
and only one is needed, to use all of them anyway by
simply connecting them in parallel. This reduces any Fig. 7.3b: Typical relay
contact-wear problems.

I actually recommend avoiding mechanical relays altogether. If you want to switch a DC


voltage and can use the same ground potential, transistors are always the better
choice. If, on the other hand, you want to switch a mains voltage, and thus naturally
require isolation, there is also a better solution, the electronic relay – a so-called
solid-state relay:

106

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 103 2022-07-27 20:20


Chapter 7 • How do you switch something?

7.3.1 Solid-State relay


The solid-state relay (SSR for short) switches elec-
tronically via internal triacs or similar. Neverthe-
less, it is completely galvanically isolated, like a
mechanical relay, because the control takes place
using light, via an integrated optocoupler.

Often the internal circuit is very complex and


optimized for switching mains voltage, so that e.g.
it is always switched at the AC’s zero-crossing
voltage. The circuit symbol on the right is therefore Fig. 7.3.1a: Solid-state relay
only symbolic and does not show the real inner
workings.
Since these relays have no mechanical contacts, they are
generally very reliable and durable. The only disadvantage:
When switched on, a small residual voltage remains at the
switching pins (depending on the relay type and the load),
but this is of no consequence at mains voltage, which is very
high anyway.

Small versions, like in the picture on the left, do not need a


heat sink and can switch up to 1 A, 2 A, or 5 A, depending
on the design. They are often also available in several
Fig. 7.3.1b: SSR up to 2 A versions, with different control voltages, e.g. 5 V or 12 V.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 104 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Chapter 8 • Controlling, regulating, and dimming


Sometimes we need more than just a simple on/off switch, e.g. if we want to control
the brightness of LEDs, i.e. if the Arduino should provide a controllable voltage or
current. Unfortunately the ATmega328 has only analog inputs. The outputs, on the
other hand, can only be switched digitally, either on or off. Therefore we have to reach
into the bag of tricks.

8.1 Pulse-width modulation (PWM)


By means of pulse width modulation (PWM for short), sometimes also called pulse
duration modulation (PDM), we can obtain an apparently analog signal from a digital
one, and here's how it works:

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%.

Fig. 8.1: Pulse-width modulation 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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 105 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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.

8.1.1 Project: Dimming LEDs in all colors


A popular application for pulse-width modulation is the dimming of LEDs, power LEDs,
and LED lamps. If the primary colors, red, green, and blue, are mixed, any color can be
created.

With small LEDs, the circuit looks


something like in Fig. 8.1.1. For
the blue LED, we may use a
slightly lower series resistor than
for red and green, because the
blue LED has a higher forward
voltage, and thus less current
would otherwise flow through it.

If you want to use power LEDs,


e.g. a 10-watt RGB LED or an
RGB LED string, you can use the Fig. 8.1.1: Colored LEDs on the PWM pins
circuit in Fig. 7.2.5c on page 105.
In this case, a 12 V power supply of the appropriate current is required. However,
nothing changes in the control, because in both versions (Fig. 8.1.1 and Fig. 7.2.5c) the
LEDs light up on a HIGH signal.

109

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 106 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

8.1.2 Quick color theory


Some may wonder why the LED colors red, green, and blue are needed, when inkjet
printers use different primary colors for mixing. The reason is that LEDs generate light,
whereas printing inks can only absorb light. In other words: without light, it’s dark, i.e.
black, while with no ink, the paper remains white. If you mix all LED colors, you get
white light (additive color). But if you mix all of the printing inks, you get black
(subtractive color).

Fig. 8.1.2: Additive vs. subtractive color mixing.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 107 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

8.1.3 Flowing color changes


This small program creates slowly flowing color transitions. It can be used with small
LEDs (as in Fig. 8.1.1 on page 109), as well as with power LEDs or RGB LED strips (as
per Fig. 7.2.5c on page 105). It constantly changes the brightnesses of each of the
three LEDs in a continuous sinusoidal procession, in such a way that there is never (or
not for a very long time) a repetition of the exact same color.

Before we get to the sketch, a few words about the sine and cosine functions, because
we use the cosine here.

Syntax: sin() and cos()

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 108 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Flowing color changes 8_1_3.ino

#define TWO_PI 6.2831853

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 109 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

brightness = round(127.5 - (127.5 * cos(pos[color] * TWO_PI))); // calculate


analogWrite(pin[color], brightness); // brightness as PWM at color’s pin

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).

color++; // next color (0 to 2)


if (color >= 3) // if overflow (i.e. greater than 2)
{
color = 0; // start again with color 0
}
}

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.

Tip: Small test with LED effect board


If you built the LED effect board (with LEDs on Pins 9, 10, and 11), you can also test
the Flowing color changes sketch. This way, you can see the different quick brightness
processions, even if without colorful RGB colors.

113

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 110 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

8.2 Low-pass demodulation


It is also possible to generate a real DC voltage from a
pulse-width modulated signal. In the simplest case, all we
need is a resistor and a capacitor, which we connect together
to form a low-pass filter, as shown in the picture on the
right.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 111 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

This corresponds to the green line here. After


6 pulses, where the green line ends, it has
reached the final value of 75%. After one
pulse, at the small vertical green mark, the
capacitor voltage (red) is still nearly as high
Fig. 8.2d: Initial rise seen in green as the green line, but the slope is decreasing.
(If you look closely, you can see that during
the first pulse, the red line is even steeper than the green line, but, as long as the pulse
is active, the capacitor voltage is heading toward 100%. If we extended this slope as a
line, we’d get to 100% after the 6th time period).

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:

ΔU = 5 V / (4  490 Hz  4700 Ω  0.0001 F) = 0.0054277 V = 5.4277 mV

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 112 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

8.3 Regulation with a feedback loop


We can control voltages even more precisely by adding
feedback control. To do this, we simply apply the output
signal to one of the analog input pins. During program
execution we can measure the actual voltage repeatedly
and correct the PWM signal accordingly. In this way,
voltages can be set even more precisely and changes may
also be achieved more quickly. Fig. 8.3:
PWM with feedback

8.4 Project: Adjustable constant current source


This circuit provides a precisely adjustable constant current. It is very well suited to
supply power LEDs, for example, with constant current, or also to charge rechargeable
batteries. For lithium-ion batteries, however, the next circuit (8.5) should be used.

There is no construction plan here, but


We need:
you can use the next circuit’s (8.5)
• Pro Mini Board (ATmega168 or 328) construction plan for this version, too,
• T1 BD435 (possibly with heat sink) and build the circuit with a PCB or
• R1 100 Ω / ¼ W freely wired with a few thumbtacks on
• R2, R3 22 Ω / ¼ W a small board. The component num-
• R4 1 Ω / 1 W (see text: “adjustment”) bers are identical to those in this ver-
• C1, C2 1000 µF / 10 V sion.

116

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 113 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

The picture on the left shows the use of the circuit


board of circuit 8.5. All gray components are not used
here yet. The load is connected to B-. At B+, the board
is supplied via the RAW connector. This pin is also the
positive pole for the load, if its voltage supply is not
above 12 volts.

The technical explanations of these circuits, 8.4 to 8.6,


can be quite detailed and are not always easy to under-
stand. But, you don't have to understand their full
functionality to rebuild and use them. From Chapter 9
onward, it gets easier again.

Fig. 8.4a: Built with 8.5 PCB

The RC filters at the PWM signal are of


much lower impedance than in the
previous examples, because some
current is needed here to drive the
transistor. The amplified current at the
emitter also flows through R4, causing
a corresponding voltage drop across it.
This voltage in turn raises the emitter
potential of the transistor. This again Fig. 8.4b:
Adjustable current source using PWM
counteracts the base-emitter voltage
and the current.

In relation to R4, transistor T1 thus works as a common-collector circuit, also known as


an emitter-follower. The voltage at R4 always accords with the base voltage, but always
distanced from the base-emitter voltage, which remains relatively constant at approx.
0.7 V (or perhaps a bit more, depending on the current).

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 114 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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 table shows the range of measure-


Current (max) Shunt R4
ments achievable for each value of R4.
Also borne in mind is the fact that we up to 800 mA (1 A) 1Ω/1W
don’t want to operate the resistors right at up to 1 A 1Ω/2W
their limits. The values in parentheses, on
up to 1.2 A (1.4A) 0.47 Ω / 1 W
the other hand, show the full load capacity
of the respective resistors. (For 1 Ω / 2 W up to 1.7 A (2A) 0.47 Ω / 2 W
this value is missing, because the drop up to 2 A (2.4A) 0.33 Ω / 2 W
would be over 1 V).

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 115 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

Syntax: Bitwise operators


a = 21 & 7;

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 116 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Adjustable constant-current source 8_4.ino

#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.

int set_current = 40; // current setting in mA (example)

byte n = 0; // count variable (for loop passes)


float current; // actual current

unsigned int current_16 = 0; // current value 16-bit from 64 measurements


unsigned int pwm_14; // PWM settings 14 bit (64 times 8 bit summed)
float current_16_factor; // conversion factor for current

float average_current = 0; // average current within 1 second


float average_pwm = 0; // average pulse width modulation (max. 8160)
float average_set = 0; // average set current
int count_average = 0; // number for each individual average value

unsigned long system_time; // system time in milliseconds


unsigned long display_time; // display time in milliseconds
int refresh_time = 1000; // display always after 1000 ms

int pwm = 0; // current pulse width modulation


byte last_pwm = 0; // prior pulse width modulation
float fine_pwm = 0; // fine value of the pulse width modulation

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 117 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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

Serial.begin(38400); // set up serial transmission


analogReference(INTERNAL); // use internal reference for ADC
current_16_factor = (float(reference) / 65536) / r4; // calculate factors

system_time = millis(); // system time in milliseconds


display_time = system_time + refresh_time; // display time in milliseconds
}

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;

n++; // count loop passes


if (!(n & 63)) // at every 64th pass
{

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 118 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

current = current_16_factor * current_16; // calculate current


average_current += current; // sum average current values
average_set += set_current; // sum average current setpoint values
average_pwm += pwm_14; // also sum PWM setpoint
count_average++; // count summations

current_16 = 0; // reset values for new (64-fold) summation


pwm_14 = 0;

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.

if (!set_current) // if no current is set (zero)


{
pwm = 0; // no pulses
}
else // current should flow
{
fine_pwm += (set_current - current) * 0.025 * r4; // adjust PWM
pwm += round (fine_pwm); // change PWM by integer value
fine_pwm += last_pwm - pwm; // Subtract change (leave only +/- 0.5)

if (!last_pwm) // if previously was switched off


{
pwm += 34; // add approximate minimum value
}
if (pwm > 255) // limit PWM to maximum 255
{
pwm = 255;
}
else if (pwm < 0) // PWM at least 0 (not negative)
{
pwm = 0;
}
}
if (pwm != last_pwm) // if value has changed
{
analogWrite(pwm_pin, pwm); // modify output signal
last_pwm = pwm;
}

122

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 119 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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.

if (pwm && pwm < 255) // if switched on and regulation is working


{
digitalWrite(led_pin, HIGH); // LED on
}
else
{
digitalWrite(led_pin, LOW); // LED off
}
}

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.

system_time = millis(); // read current time


if (display_time <= system_time) // when time unit expired
{
display_time += refresh_time; // Set new time (1 second further)

// Divide summed average values by number of summations:


average_current /= count_average;
average_set /= count_average;
average_pwm /= count_average;

123

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 120 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.println(""); // blank line


Serial.print("Current (real): ");
Serial.print(average_current, 1); // actual current with one decimal place
Serial.println(" mA ");

Serial.print("Current (set): ");


Serial.print(average_set, 1); // setpoint for current
Serial.println(" mA ");

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.

average_current = 0; // reset all average values for new summation


average_set = 0;
average_pwm = 0;
count_average = 0; // also reset the summation count
}
}

Finally, the “average_” variables and the corresponding counter are reset, so the sum
and count for the next second can restart.

124

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 121 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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.

At first, we need a load and possibly also a


separate voltage source for this load. We
Fig. 8.4c: Current displayed
have to consider that, in power resistor R4
in Serial Monitor
and in the transistor (between collector and
emitter) a minimum voltage of a total of up to 1.4 V (at 1 A) drops, depending on the
current. The supply voltage for the load should therefore ideally be at least 1.5 V higher
than that required by the load.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 122 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Next, we need as accurate a multimeter or millivoltmeter as possible with which to


measure the voltage drop across R4. At 1 Ω, one mV corresponds to one mA, so we
should measure about 40 mV at 40 mA. Now we can do the math:

reference = reference_old  actual_voltage / set_current

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:

reference = reference_old  actual_voltage / R4 / set_current

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 123 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

8.5 Project: Lithium-ion battery testing and charging station


This circuit is for precise charging, testing and analyzing of lithium-ion batteries, e.g.
18650 cells. However, only single or parallel cells can be used, not batteries with several
cells in series. During charging, the batteries are automatically tested and analyzed,
and the program also determines the capacity and presents it exactly in milliampere
hours (mAh). In addition, the batteries’ internal resistance is determined and displayed.
After charging, the sketch monitors the battery for a few hours and shows how many
millivolts are lost. All of this data says a lot about the quality of the battery.

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.

8.5a: Component placement Fig. 8.5b: Charging station circuit diagram

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.

Another possible use of the circuit is as a high-quality adjustable constant-current


source. For this, there’s a separate program sketch and small component changes are
required. But, we will come to that later in section 8.6.

127

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 124 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Construction on a PCB
We need:

• Pro Mini board (with ATmega328)


• T1 BD435 (possibly with heat sink)
• R1 100 Ω / ¼ W
• R2, R3 22 Ω / ¼ W
• R4 1 Ω / 1 W (see text, “Customizing”)
• R5 22 kΩ / ¼ W / 1%
• R6 4.7 kΩ / ¼ W / 1%
• R7 1 MΩ / ¼ W / 1%
• R8 220 kΩ / ¼ W / 1%
• C1, C2 1000 µF / 10 V
• C3 100 nF / 50 V
Fig. 8.5c: PCB layout at original size
(seen from above) In addition, depending on construction:

For assembling on a PCB, the PCB lay-


• Board and 9 thumbtacks or a PCB
out is shown in Fig. 8.5c and the corre-
• a serial USB adapter with wires
sponding assembly plan in Fig. 8.5a,
• possibly an 18650 battery holder.
from which we can build the circuit. The
board data can also be downloaded in
Gerber format from the Elektor website at elektor.com/20243. You might also be able to
purchase the board on Ebay. There for example you can search for "Lithium-ion battery
charging station" and the book title.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 125 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

Construction using thumbtacks


The wiring of a circuit on a wooden board with the help of a few thumbtacks is, in my
opinion, quite wrongly considered primitive and outdated, because, with this circuit we
really don’t need more than a small board (at least 7 cm  5.5 cm) and 9 thumbtacks
for a stable construction. The wood thickness should be preferably over 8 mm, so that
the 8 mm-long thumbtacks do not stick through the bottom.

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.

It is best to start with R8. This resistor’s leads


are bent to a “U” and soldered to A1 and GND. To
get it in the right position, you will probably have
to cut only a tiny bit off the leads. Solder it to
A1, GND, and the two dot markers on the
thumbtacks. The first soldering point is always
the most difficult, because you have to hold the
component exactly in place. A small clamp may
help to hold the component in place before the
first soldering. It’s best to tin a drop of solder on
the tacks beforehand. To do this, hold the
soldering tip over a large area and add a little
solder until it starts to melt onto the tack. Then,
remove the soldering iron again immediately.
Beforehand, we should also lightly tin the leads of
the components that are soldered later.

Fig. 8.5d: Construction plan for


thumbtacks

129

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 126 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Fig. 8.5e: Constructing the charging station using thumbtacks

130

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 127 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 128 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Supplying power via USB


When using it as a test and charging station for lithium-ion batteries, the question
arises, depending on the required current, how we supply power. Up to about 400 mA,
there should be no problem supplying the circuit via the USB adapter, if we use short
cables of less than 1 meter (if possible, soldered to the board as well). We can also use
the same cable length and thicker wires (e.g. 0.75 mm² twin stranded wire) to add a
second USB cable as an additional power supply. The second USB connector is then
simply plugged into a free USB socket on the PC. This is sometimes seen with portable
hard drives (which have a higher power consumption). This way, we can draw up to
about 1 amp. Of course, if you are using a laptop while doing this, you should use it
while connected to mains, or you’ll drain the laptop battery quickly.

Separate power supply


Another option is to use your own power supply, which should ideally supply about 5 to
5.5 V. If the power supply is over 5.6 V (maximum, e.g. in idle), R6 should be changed
to 3.9 kΩ. This ensures that the voltage can still be measured. The change of R6 must
then of course also be amended, in the sketch’s definitions, from 4700 to 3900. Then
you can also raise the over_voltage variable from 5700 (5.7 V) to 6700 (6.7 V). Even
then, the voltage must not go higher than 6.7 V. The maximum current that the power
supply can deliver should be slightly higher than the maximum charging current
required. For example, if we want to charge at a maximum of 2 A, a power supply of
5 V (or max. 6 V) / 2.5 to 3 A is ideal.

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.

Bridge between RAW and VCC


In the schematic there is a dashed connection between VCC and RAW, which we always
need if the board is supplied with voltage via the USB interface and this voltage is also
used for the power supply (i.e. if no external power supply is used). The connection is
then very important, so that not only the board is supplied with voltage, but the entire
circuit.

132

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 129 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 130 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Sketch: Lithium-ion testing and charging station 8_5.ino

#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 end_voltage = 4200; // final charging voltage in mV


int power_current = 400; // (max.) charge current in mA

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 131 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

float current; // actual current


float current_limit = 9999; // not limited yet
float current_limit_2 = 9999; // not limited yet
float ic; // collector current
float ib; // base current
float set_current = 0; // setpoint for current
int middle_current; // between end_current and power_current
float last_current; // previous current

float collector; // collector voltage


float supply; // board voltage
float battery; // battery voltage
float full_voltage; // battery voltage 1 min after turning off
float uce; // collector-emitter voltage of T1
float ue; // emitter voltage of T1 against ground
float last_voltage; // previous voltage

float average_current = 0; // average current within 1 second


float average_supply = 0; // average board voltage
float average_battery = 0; // average battery voltage
float average_collector = 0; // average voltage at collector
float average_pwm = 0; // average pulse width modulation (max. 8160)
float average_set = 0; // average setpoint for the current
int count_average = 0; // number of single values for average values

unsigned int current_16 = 0; // value of current (16 bit, 64 measurements)


unsigned int collector_15 = 0; // collector voltage 15 bit, 32 measurements
unsigned int supply_15 = 0; // board voltage 15 bit from 32 measurements
unsigned int pwm_13; // PWM settings 13 bit (32 times 8 bit summed)

float current_16_factor; // conversion factor for current


float collector_15_factor; // conversion factor for collector voltage
float supply_15_factor; // conversion factor for board voltage

unsigned long system_time; // system time in milliseconds


unsigned long display_time; // display time in milliseconds
unsigned long start_time; // start time in milliseconds
unsigned long end_time; // end time in milliseconds
unsigned long duration; // seconds elapsed since start time
int refresh_time = 1000; // display always after 1000 ms
byte days; // days elapsed since start time
byte hours; // hours elapsed (0 to 23)
byte minutes; // minutes elapsed (0 to 59)
byte seconds; // seconds elapsed (0 to 59)

// Times (in minutes) for text output after full charge:


unsigned int control_time[8] = {1,60,120,240,480,960,1920,3840};

135

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 132 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

unsigned long energy = 0; // charge already done in µA min


float pt1; // actual power dissipation in the transistor (T1)
float pr4; // actual power dissipation in R4
float rb; // sum of base resistors before T1
float ri; // Internal resistance of the battery

byte mode = 0; // 0->no battery , 1->empty , 2->rising, 3->power, 4->reduced, 5-


>full, 6->undervoltage, 7->overvoltage
byte n = 0; // count variable (for loop passes)
byte counter = 0; // count variable (for ri calculation and after charging for text
outputs)
int pwm = 0; // actual pulse width modulation
byte last_pwm = 0; // previous pulse width modulation
float fine_pwm = 0; // fine value of pulse width modulation
byte led = 0; // LED lighting duration 0 to 200 (also 253 and 254 for errors)
byte full_counter = 0; // counts end current until it overflows, then charging is finished
byte led_time; // counts every second from 200 back to 0 (for LED control)
bool led_on = false; // LED state, true while charging
float v_factor = -2; // actual calculated gain factor of T1 (negative is invalid)

The other variables are self-explanatory from their comments.

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);

Serial.begin(38400); // set up serial transfer


analogReference(INTERNAL); // use internal reference for ADC
current_16_factor = (float(reference) / 65536) / r4; // calculate factors
collector_15_factor = (float(reference) / 32768) * (r7 + r8) / r8;
supply_15_factor = (float(reference) / 32768) * (r5 + r6) / r6;
middle_current = round(sqrt(float(end_current) * power_current));
rb = r1 + r2 + r3 + r_be; // sum base resistors
Serial.println(" "); // Output basic settings
Serial.println("Settings:");
Serial.print("max ");
Serial.print(end_voltage / 1000.0); // end voltage (in volts as float)
Serial.println(" V");
Serial.print("max ");
Serial.print(power_current); // maximum charging current
Serial.println(" mA");
Serial.println("");
system_time = millis(); // system time in milliseconds
display_time = system_time + refresh_time; // display time in milliseconds
start_time = system_time; // start time in milliseconds
}

136

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 133 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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);

n++; // count loop passes


if (!(n & 31)) // at every 32nd pass
{

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.

current = current_16_factor * current_16; // calculate actual current


collector = collector_15_factor * collector_15; // calculate collector voltage
supply = supply_15_factor * supply_15; // calculate board voltage
current_16 = 0; // reset values for new (32-fold) summation
collector_15 = 0;
supply_15 = 0;

battery = supply - collector; // battery voltage


average_current += current; // sum average values
average_supply += supply; // (summations run for 1 second each time)
average_battery += battery;
average_collector += collector;
average_pwm += pwm_13; // also sum PWM values
count_average++; // count summations
pwm_13 = 0; // also reset

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 134 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

current_limit_2 = set_current + 2; // limits eventual current increase


if (supply > over_voltage) // if board voltage is too high
{
set_current = 0;
mode = 7;
led = 254;
}
else if (supply < under_voltage) // if board voltage is too low
{
set_current -= 1; // reduce current
mode = 6;
if (supply < under_voltage - 100) led = 253; // only at extreme undervoltage
}
else if (battery > 4300) // if no battery is inserted
{
set_current = 0;
mode = 0;
counter = 0;
led = 0;
digitalWrite(led_pin, LOW);
}
else // battery is inserted
{
if (mode == 0) // if no battery was inserted before
{
energy = 0; // reset charge
start_time = system_time; // start time now
}
if (battery < empty_voltage) // if battery is empty
{
set_current = empty_current; // first only minimum current
mode = 1;
led = 1; // minimum flash duration
}
else if (battery < power_voltage) // rising phase
{
set_current = (battery - empty_voltage) / (power_voltage - empty_voltage);
led = 2 + (set_current * 48); // 2 to 50
set_current *= (power_current - empty_current);
set_current += empty_current;
mode = 2;
}
else if (mode != 5) // if battery is not full yet (still charging)
{
if (battery <= end_voltage) // power phase (maximum charging current)
{
if (set_current < power_current) // if below maximum current
{
if (mode == 4) // if end voltage has already been reached

138

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 135 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

{
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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 136 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

}
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

average_set += set_current; // summing average values for setpoint current

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.

if (!set_current) // if no current setpoint (zero)


{
pwm = 0; // no pulses
}
else // current shall flow
{
fine_pwm += (set_current - current) * 0.07 * r4;

pwm += round (fine_pwm); // change PWM by integer value


fine_pwm += last_pwm - pwm; // subtract change (leave only +/- 0.5)

if (pwm > 255) // limit PWM to maximum 255


{
pwm = 255;
}
else if (pwm < 0) // PWM at least 0 (not negative)
{
pwm = 0;
}
}
if (pwm != last_pwm) // if value has changed
{
analogWrite(power_pin, pwm); // modify output signal
last_pwm = pwm;
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 137 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

system_time = millis(); // read actual time


led_time = 200 * (display_time - system_time) / refresh_time;
// led_time decreasing from 200 to 0 (within 1s interval)
if (led > 0 && led < 200) // if LED in normal flash mode
{
if (!led_time) // if time elapsed to 0
{
digitalWrite(led_pin, LOW); // LED off
led_on = false;
}
else if (led >= led_time && !led_on) // if time to LED value elapsed
{
digitalWrite(led_pin, HIGH); // LED on
led_on = true;
}
}
else if (led == 253) // if error (undervoltage, fast blinking)
{
led_time -= 50 * (led_time / 50); // 4 time units (49 to 0 decreasing)
if (led_time == 20) // on at 20
{
digitalWrite(led_pin, HIGH);
}
else if (!led_time) // off at 0
{
digitalWrite(led_pin, LOW);
}
}
else if (led == 254) // if error (overvoltage, very fast blinking)
{
led_time -= 25 * (led_time / 25); // 8 time units (24 to 0 decreasing)
if (led_time == 9) // on at 9
{
digitalWrite(led_pin, HIGH);
}
else if (!led_time) // off at 0
{
digitalWrite(led_pin, LOW);
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 138 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

if (display_time <= system_time) // if time unit expired


{
display_time += refresh_time; // Set new time (1 second forward)
// Divide summed average values by number of summations:
average_current /= count_average;
average_supply /= count_average;
average_battery /= count_average;
average_collector /= count_average;
average_set /= count_average;
average_pwm /= count_average;

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.

if (mode != 5 || ic >= 0.05) // if still charging or current is still flowing


{
// calculate further values based on average values:
ue = average_current * r4;
uce = average_collector - ue;
ib = ((average_supply * average_pwm / 8160) - (ue + u_be)) / rb;

if (uce < 120 + (average_current / 2.5) || average_pwm > 8000) // saturation


{
v_factor = -1; // invalid (because of saturation)
ic = average_current - ib; // collector current = emitter current - base current
}
else if (ib < 0.5/r4) // Base current too small (and thus too inexact) to calculate
{
v_factor = -2; // invalid (because inexact)
ic = average_current * 0.99; // collector current estimated (ampl. factor 100)
}
else // base current and amplification factor calculable
{
ic = average_current - ib; // collector current = emitter current - base current
v_factor = ic / ib; // amplification factor
}
energy += round(refresh_time * ic / 60); // charge in µA min (add 1 second)

142

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 139 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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.

pt1 = average_current * uce / 1000; // T1 Power dissipation in mW


pr4 = average_current * ue / 1000; // R4 Power dissipation in mW
if (pt1 > p_t_max) // power overload (transistor)
{
current_limit_2 = float(p_t_max) * 1000 / uce; // limit power dissipation
}
else
{
current_limit_2 = 9999; // do not limit
}
if (pr4 > p_r_max) // power overload (R4)
{
current_limit = sqrt(float(p_r_max) * 1000 / r4); // limit power dissipation
}
else
{
current_limit = 9999; // do not limit
}
if (current_limit_2 < current_limit)
{
current_limit = current_limit_2; // use smaller limit
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 140 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

duration = (system_time - start_time) / 1000; // duration since charging start


if (duration > 900 && average_current > 50 && average_battery > end_voltage -
200 && !counter)
{
// Start measurement of internal resistance Ri:
last_current = average_current; // save values for later use
last_voltage = average_battery;
ri = 0; // reset for summation
counter++; // counts seconds for 1 minute (as long Ri measurement runs)
current_limit = 0; // switch off charge
}
else if (counter < 59 && counter) // while measurement is running (1 to 58)
{
if ((counter&3) == 1) // Second 1, 5, 9, 13, 17 ...
{
current_limit = 0; // keep switched off
}
if ((counter&3) == 2) // Second 2, 6, 10, 14, 18 ...
{
ri+= (last_voltage - average_battery) / (last_current - average_current);
set_current = last_current + 10; // set high (avoid limitation)
last_current = average_current; // save values to use later
last_voltage = average_battery; // (current should be 0 here)
current_limit = 9999; // switch on again
}
else if (!(counter&3)) // Second 4, 8, 12, 16, 20 ...
{
ri+= (average_battery - last_voltage) / (average_current - last_current);
last_current = average_current; // save values to use later
last_voltage = average_battery; // (current should flow here)
current_limit = 0; // switch off again
}
counter++; // count up one second
}
else if (counter == 59) // finish measurement after 59 seconds
{
ri /= counter >> 1; // divide by number (half counter value, rounded down 29)
counter++; // to 60
}
days = duration / 86400; // calculate single values from total duration
duration -= days * 86400;
hours = duration / 3600;
duration -= hours * 3600;
minutes = duration / 60;
duration -= minutes * 60;
seconds = duration;

144

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 141 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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.

// Output relevant data and average values of the last second:


Serial.println(" ");
Serial.print("Charge: ");
Serial.print(float(energy) / 60000, 3); // µA minutes to mAh, 3 decimal places
Serial.print(" mAh ");
if (days) // only if at least 1 day already elapsed
{
Serial.print(days);
Serial.print("d:");
}
if (days || hours) // only if at least 1 hour already elapsed
{
if (hours < 10) // if value is only one digit
{
Serial.print("0"); // prefix "0"
}
Serial.print(hours);
Serial.print("h:");
}
if (minutes < 10) // if value is only one digit
{
Serial.print("0"); // prefix "0"
}
Serial.print(minutes);
Serial.print("m:");
if (seconds < 10) // if value is only one digit
{
Serial.print("0"); // prefix "0"

145

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 142 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

}
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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 143 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 144 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

{
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.

else // If battery is full and no more current is flowing:


{
if (counter > 10) counter = 0; // reset (old value from Ri measurement)
if (counter < 8) // if not all 8 infos have been displayed yet
if (millis() >= end_time + long(control_time[counter]) * 60000)// info time
{
if (!counter) // First info (after 1 minute) if counter is still at 0
{
full_voltage = average_battery; // save actual voltage
Serial.print("Fully charged: ");
Serial.print(full_voltage / 1000,3); // voltage indication with 3 decimal places
Serial.println(" V (after 1 min)");
}
else // if first info was already displayed
{
Serial.print("Self-discharge: ");
Serial.print(round(full_voltage - average_battery)); // voltage loss in mV
Serial.print(" mV after ");
Serial.print(control_time[counter]/60); // calculate number of hours
if (counter == 1) Serial.println(" hour"); // singular
else Serial.println(" hours"); // or plural
}
counter++; // count on for next message
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 145 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

average_current = 0; // reset all average values for new summation


average_supply = 0;
average_battery = 0;
average_collector = 0;
average_set = 0;
average_pwm = 0;
count_average = 0; // reset also the number of summations
}
}

Finally, the “average_” variables and the corresponding counter variable are reset and
ready for the next second’s values.

Current and voltage specifications


Besides the exact resistor values in the sketch’s definitions, there are also numerous
variables given in the first two variable blocks, where we can place all the necessary
current and voltage settings:

int end_voltage = 4200; // final charging voltage in mV

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.

int power_current = 400; // maximum charging current in mA

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 146 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

The graph shows the voltage horizontally and the charging current vertically (from the
black zero line):

Fig. 8.5f: Charging curve

If the battery is completely empty – i.e. below empty_voltage (2.8 V) - only a


minimum current of empty_current (20 mA) flows at first. As soon as voltage
empty_voltage is reached (which happens very quickly), the charging current increases
continuously up to a level of power_current (400 mA) at a voltage of power_voltage
(3.7 V). This voltage is also usually reached relatively quickly.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 147 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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 window shows the display a


few hours after charging. The
upper seven lines are updated
every second as long as the bat-
tery is charging. When the
battery is full, the lower lines
appear, gradually.

In the first line, you see the total


charged current in milliampere
hours, as well as the charging Fig. 8.5g: Serial Monitor output
duration.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 148 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Another quality criterion is the internal resistance:

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 149 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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:

reference = reference_old  actual_voltage / R4 / Monitor_current

reference_old is the previous reference specification (1100). The actual_voltage is the


voltage at R4, which we measure using our multimeter. R4 is the resistance value itself,
i.e. in ohms. Monitor_current means the current that Serial Monitor shows us in the
second line. We now enter the result (rounded to the nearest integer) as the new
reference value in the sketch and load it onto the board. It must be between 1000 and
1200, else something is wrong.

153

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 150 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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:

reference = reference_old  actual_voltage / Monitor_voltage

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 151 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

8.6 Project: Adjustable current source with limits


With the same construction as the charging station (section 8.5) we can also create a
universal current source, which can be used almost like an adjustable laboratory power
supply. The circuit can be operated with voltages up to 31 volts. For this, only two
resistors (R5 and R8) have to be re-specified. In addition to an adjustable current, the
voltage and/or the power of the load can also be limited to a predefined value here.

Component specification

Voltage R5 R8 C3 A significant difference is resistors R5 and R8,


because these must now be chosen according
Max. 5.5 V 22 k 220 k to the maximum used operating voltage per
Max. 6.5 V 27 k 180 k 1000 µF the table. In addition, we must ensure that
electrolytic capacitor C3 can handle the oper-
Max. 7.5 V 33 k 150 k
ating voltage.
Max. 9.0 V 39 k 120 k
max. 11 V 47 k 100 k Voltage For R4 here, 1 ohm / 2 watts is provided,
which allows for the control of a maximum
max. 13 V 56 k 82 k depends
current of 1 amp. For other ranges of current,
max. 15 V 68 k 68 k on max. we can use the table on page 118. For higher
max. 18 V 82 k 56 k operating currents and high voltage differences between
input and output, a larger heat sink for T1 is
max. 22 V 100 k 47 k voltage required, whereby the permissible T1 power
max. 26 V 120 k 39 k dissipation in the sketch must be adjusted.

max. 31 V 150 k 33 k

Time vs charge amount


In addition, the program also offers the ability to limit the delivered current to a certain
duration or a predefined charge quantity (in milliampere hours). This can be very useful
when charging NiCd, NiMH, or lead-acid batteries, because there, the charging process
is not as simply limited by voltage. Instead, it usually makes more sense to limit the
charge amount to the required mAh value. Once this charge amount is reached, the
program simply switches the circuit off.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 152 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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:

Fig. 8.6a: Circuit diagram, adjustable constant current source

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 153 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

Sketch: Adjustable current source with limits 8_6.ino

#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.

int max_voltage = 12000; // limit load voltage (12 volts)


int max_current = 300; // limit load current (0.3 amps)
int max_power = 2000; // limit load power (2 Watt)
int max_charge = 0; // limit load charge in mAh (0 = unlimited)
unsigned int max_time = 0; // max. time in seconds (0 = unlimited, max. 65535)
int p_t_max = 750; // maximum power dissipation (in mW) in the transistor
int p_r_max = 750; // maximum power dissipation (in mW) in R4
boolean on = true;

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 154 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

[..]
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.

Fig. 8.6b: Serial output


158

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 155 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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.

The table on the left shows the various


Mode Description Abbr.
operating mode abbreviations displayed in the
0 turned off OFF Serial Monitor. OFF means switched off or that
1 constant voltage CV the “on” variable is set to 0 or “false.” CV
(constant voltage) limits the voltage. CC
2 constant current CC
(constant current) limits the current, and with
3 constant power CP CP (constant power) you can also specify the
4 short-circuit SC power in the load. You only have to set voltage
and current a little higher (or not limit them at
5 overload OL
all, using 0). Then, the load is regulated to the
6 saturated transistor SAT specified power. SC (short circuit) is displayed
7 undefined UD when the voltage at the load is below one volt
with current limitation. OL (overload) means
that the transistor (or resistor) is at its load limit, and therefore the current is reduced.
SAT (saturation) is indicated when the desired limit cannot be reached at all, because
the transistor is in saturation and is therefore being fully-driven without reaching the
current, voltage, or power limit. The operating voltage is then too low, because the
output voltage is only marginally lower than it.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 156 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Default values, limits


With variables max_voltage, max_current, max_power, and max_charge, we can limit
the voltage, the current and/or the power, as well as the charge amount at which the
charging circuit is to be switched off, if necessary.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 157 2022-07-27 20:20


Chapter 8 • Controlling, regulating, and dimming

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:

reference = reference_old  actual_voltage / R4 / Monitor_voltage

reference_old is the previous reference specification (1100). actual_voltage is the


voltage at R4, which we measure using our the multimeter. R4 is the resistance value,
and Monitor_current means the current that the serial monitor shows us in the first line.
We now enter the result (in millivolts rounded to the nearest integer) as the new
reference value in the sketch. It must be between 1000 and 1200.

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:

reference = reference_old  actual_voltage / Monitor_voltage

Attention: If we have already performed calibration, reference_old is now no longer


1100, but the already-changed value. The actual_voltage is, again, the voltage
measured with the multimeter, which we now best measure not at the load, but directly
at the circuit, at the B+ and B- terminals. Monitor_voltage is the output voltage, which
Serial Monitor shows us in the first line.

161

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 158 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 159 2022-07-27 20:20


Chapter 9 • Controlling motors

Chapter 9 • Controlling motors

Fig. 9: Motor with gears and mini stepper motor

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?

9.1.1 Transistor control


From page 101 in sections 7.2.3 and 7.2.4, we already got familiar with different
transistors, and these are able to switch common DC motors. We can use MOSFETs or
bipolar transistors. The picture on the next page shows three examples.

163

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 160 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

9.1.2 Speed control using PWM


If we want not only to switch the motor, but also control its speed, this can be done
very well using pulse-width modulation (PWM). Instead of supplying the motor a lower
voltage, we simply switch it on and off several hundred times per second, as already
described in section 8.1 on page 108.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 161 2022-07-27 20:20


Chapter 9 • Controlling motors

9.1.3 Forward and reverse with an H-bridge


It gets a bit more complicated if we want to control a
DC motor in both directions, because for this we need a
so-called H-bridge. The picture shows symbolically how
it works. The motor’s two connections can each be
switched to the positive or negative pole. In this way,
polarity reversal is also possible, depending on which
line is connected to positive while the other is connected
to negative. Fig. 9.1.3a: H-bridge

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

You can also connect the two channels together by tying


the corresponding lines of the two H-bridges together. This
way you can control only one motor, but at up to 1.5 A. If
only one motor is used, I generally recommend using both
H-bridges anyway, because if such a motor runs hard, or
even at startup, the current can be unexpectedly high. If
one is not careful with the current, the L9110S quickly
Fig 9.1.3c: Similar version
becomes a fuse and simply burns out.
of the board
165

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 162 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Fig. 9.1.3d: L298N board

The picture on the left shows the


board’s connections. For each of the 4
outputs there is the respective input
pin, whose signal determines whether
the output is HIGH or LOW. Next to
these 4 input pins, there is another pin
on the left and one on the right – ENA
and ENB – for the enable signals. ENA
Fig 9.1.3e: Connections to the L298N board must be HIGH to activate outputs 1
and 2, and ENB activates 3 and 4
accordingly.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 163 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

9.1.4 Full control using H-bridge and PWM


Now we can combine both types of control (H-bridge and PWM). This way, we get a
control that is adjustable in both directions, seemingly from -100%, through 0, to
+100%.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 164 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Syntax: min() and max() functions

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.

Sketch: Full motor control 9_1_4.ino

#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

int power = 0; // actual motor power (from -255 to +255)


byte threshold = 10; // threshold from which it will be switched on

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 165 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

if (power >= 0) // at standstill or forward


{
analogWrite(motor_pin_a, min(power, 255)); // set signal A to 0 to 255
analogWrite(motor_pin_b, 0); // set signal B to 0
}
else // backward
{
analogWrite(motor_pin_b, min(-power, 255)); // set signal B to 0 to 255
analogWrite(motor_pin_a, 0); // set signal A to 0
}
}

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.

This is how the corresponding schematic


looks when we control the motor using a
potentiometer. Depending on the power
required, we can use either the L9110S
or the L298N board as an H-bridge. The
5-volt connection is also dotted here.
After all, it is only needed with the
L298N, and only if the Arduino must sup-
ply 5 V to the H-bridge, or vice versa. If
both have their own supply, it is omitted.
Fig. 9.1.4: Full motor control using a
potentiometer

169

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 166 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

9.2 Stepper motors

Fig. 9.2: Stepper motors of various designs and sizes

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.

9.2.1 How it works


Simplified, you can imagine a stepper motor as shown in
the picture on the right. Two coils arranged at right angles
act on the rotor, which is a magnet.

Fig. 9.2.1a:
Stepper motor

170

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 167 2022-07-27 20:20


Chapter 9 • Controlling motors

The following image shows how such a stepper motor is controlled. Eight single steps
result in one revolution of the rotor.

Fig. 9.2.1b: Controlling an 8-position stepper motor

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.

Bipolar and unipolar versions


A disadvantage of this control mechanism (with two coils) is that we have to control the
coils in a bipolar fashion, meaning in both directions. Therefore, we need two complete
H-bridges, as described in section 9.1.3, to control the motor. But, there’s another way!

171

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 168 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Fig. 9.2.1c: Stepper motors with 4 coils

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.

Full- and half-step operation


What I have described in the previous sections is the so-called half-step operation,
which is commonly used and also recommended. For the sake of completeness, I would
also like to briefly introduce the simplified full-step operation.

Fig. 9.2.1d: Stepper motor with simplified full-step control

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 169 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

Actual stepper motors


In reality, stepper motors are usually constructed a little differently – unlike in Fig.
9.2.1d, no distinction is made between coils on one side and on the opposite side. In
principle, both are the same coil, acting with one magnetic pole on one side of the rotor,
and with the other pole on the opposite side of the rotor. In the unipolar version with 4
coils, the two coils simply have two windings, or one winding with a center tap:

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 170 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Pros and cons of each version


Bipolar stepper motors (2 coils, 4 connections) are usually somewhat better and more
efficient, since they always use the entire coil, whereas unipolar operation always uses
only half the coil (from the center tap). High-quality stepper motors, for example in 3D
printers, are therefore almost always bipolar. For simple applications, however, there
are also very inexpensive unipolar motors, even with internal gears. This gives us a
resolution of thousands of steps per revolution.

9.2.2 The 28BYJ-48


The 28BYJ-48 is a unipolar stepper
motor with integrated gearbox, which
can be used for many purposes. It is
available in 5- and 12-volt versions,
and you can get it very cheaply from
China, at AliExpress, for example, for
just under 2 euro. Many offerings
even include a matching ULN2003
driver board, so that you can connect
Fig. 9.2.2a: Stepper 28BYJ-48
an Arduino directly.

The motor itself makes 32 steps per


revolution, or 64 in half-step mode, and
according to the manufacturer, the step
size is reduced by another factor of 64
by the internal gear. But, the actual ratio
is 25,792 / 405, so only about 63.68395.
But, I have also bought such motors
where the ratio was 513 / 8, or 64.125,
and supposedly there is even a version
that actually has a factor of exactly 64.
But, that’s the way it is with some
bargains: If you buy the same thing 5
times, you get 6 versions.
Fig. 9.2.2b: With driver

In half-step mode, we therefore have 64  25,792 / 405 = 4075.7728395 steps in total


per revolution, i.e. no integer value at all. In the other versions, however, there are
exactly 4104 or 4096 steps. But this doesn’t have to concern us, because, for most
applications, these small deviations don’t matter at all. Only someone who wants to
build a clock with it had better test which version was received. This can be done, for

174

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 171 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

Control using a ULN2003 driver board


This (often supplied) driver board is quite suitable
for control. It uses the ULN2003, which we
already know from section 7.2.5. With the 5 V
version of the motor, the 4 coils each have a
resistance of about 22 ohms. The 12 V version has
a resistance of about 108 ohms per coil. So, the
maximum possible current for the 5 V version is
about 200 mA, and, for the 12 V version, about
100 mA. The load is thus far within the acceptable
range for both versions, and the IC also has the
necessary freewheeling diodes already integrated.
In addition, the 4 LEDs on the driver board always
show the current switching state. 9.2.2c: ULN2003 board

175

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 172 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Control using 4 transistors


Alternatively, we can of course switch the 4 coils using 4 transistors. The BC337 is more
than adequate for this:

Fig. 9.2.2d: The 28BYJ-48 controlled using 4 bipolar transistors

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 173 2022-07-27 20:20


Chapter 9 • Controlling motors

Tip: Stepper motor under battery operation


The 5-volt version of the 28BYJ-48 stepper motor is also perfectly suited for
battery-powered devices with lithium-ion batteries. The motor still works fine with 3
to 3.5 volts, if the single step pulses are long enough, i.e. if you don't let the motor
run too fast. For switching, however, I don't recommend the ULN2003, but instead 4
transistors (as described in the previous section), because with the ULN2003 IC (due
to the internal Darlington circuit) there always remains a small residual voltage of
about 1 V to ground, so that the motor coils get even less voltage. With simple
transistors, the voltage loss is less. Likewise with the special MOSFETs, which still
switch very well with 3 volts at the low current of this motor.

For controlling the 28BYJ-48 stepper motor, here’s another sketch:

Sketch: 28BYJ-48 stepper motor control 9_2_2.ino

#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.

long motor_position = 0; // actual position


long target_position = 0; // new position
byte cycle_position = 0; // position in motor cycle (0 to 7)
unsigned int step_duration = 1500; // step duration in microseconds
unsigned int start_duration = 6000; // initial step duration in microseconds
unsigned int elapsed_time; // elapsed time in milliseconds from actual step
unsigned int last_time; // previous time
unsigned int real_duration = 0; // real actual step duration
byte motor_delay = 5; // total cycles until motor is switched off
byte motor_on = 0; // remaining cycles until motor is switched off

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 174 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

if (target_position == motor_position) // if target is reached


{
if (motor_on)
{
motor_on--; // reduce remaining cycles until deactivation
if (!motor_on)
{
digitalWrite(pin_a, LOW); // switch off all pins
digitalWrite(pin_b, LOW);
digitalWrite(pin_c, LOW);
digitalWrite(pin_d, LOW);

178

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 175 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

else // if target is not reached


{
if (real_duration > step_duration) // if acceleration phase
{
real_duration /= 1 + (float(real_duration) / 50000); // accelerate constantly
}
if (real_duration < step_duration) // if step duration would fall below minimum
{
real_duration = step_duration; // minimum duration (maximum speed)
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 176 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

if (!motor_on) // if motor is still off


{
motor_on = motor_delay; // switch on only (do not make a step yet)
}
else // if motor was already on
{
if (target_position > motor_position) // if target direction is forward
{
motor_position++;
cycle_position++;
}
else // if target direction is backwards
{
motor_position--;
cycle_position--;
}
cycle_position &= 7; // only the lowest 3 bits (0 to 7)
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 177 2022-07-27 20:20


Chapter 9 • Controlling motors

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 178 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

// 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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 179 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

9.2.3 Control using the A4988


The A4988 is a very cheap, nevertheless
high-quality chip for the exact control of
bipolar stepper motors, i.e. those which
have only 2 coils and 4 leads. The current
for the coils (and thus the force of the
motor) can also be precisely adjusted via a
potentiometer. In addition, this part can do
microstepping, a method of controlling not
only in full or half steps, but optionally in
quarter-, eighth- or sixteenth-steps. In this fig. 9.2.3a: A4988 controller
process, the two coils are not only
switched on and off, but also receive different amounts of current, step -by-step,
resulting in the corresponding intermediate steps. Many 3D printers, for example, are
controlled using this IC.

The A4988 usually comes a small board with everything you need to control it. The pins
are commonly labeled on the underside.

183

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 180 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Pinout
On the left here are the input pins that we can control with
output signals from the Arduino.

DIRECTION: A HIGH or LOW signal is applied here, which


determines in which direction the motor runs, or, more
precisely, in which direction the next step takes place when
a signal is applied to the STEP pin.

STEP: With each change from LOW to HIGH at this input,


the next step, or microstep, is carried out.

SLEEP: This input must be set HIGH at least 1 millisecond


before any motor movement. At LOW level, the motor is
Fig. 9.2.3b: Pin layout switched off.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 181 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

Adjusting the current


Before we can use the motor control, we have to
adjust the current. To do this, we first need the
value of the two shunts, which are labeled S1 and
S2 here. The value printed on the resistors, R100,
means 0.1 ohms, because R counts as the decimal
point. Don’t just count on the 0.1-ohm value, but
check it, and calculate using the correct value,
because there could be other versions of the board
9.2.3c: A4988 board adjustment
with different resistances.

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=8IR

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 182 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

9.2.4 Control using the DRV8825


The DRV8825 is very similar to the A4988. This chip
for controlling a bipolar stepper motor is also usually
offered on a small board. It can deliver a similar
amount of current to the A4988 (2.5 A peak or
1.75 A RMS) and work with a motor voltage of 8.2
to 45 volts. However, experience has shown that it
is better to use a motor voltage of only 12 volts with
this board. Otherwise, the chip has to be switched to
“fast decay,” an operating mode that the board does
not support at all, unfortunately.

Fig. 9.2.4a: DRV8825 board


Pinout
The pinout is almost the same as on the A4988 board.
Again, the input pins are on the left, and the power supply
and output pins are on the right.

DIR: The signal at this input determines the direction of


travel, i.e. in which direction the next step goes when a
pulse is sent to the STP pin.

STP: With each change from LOW to HIGH at this input, the
next step or microstep is executed.

SLP: This input must be set HIGH during operation. At LOW


Fig. 9.2.4b: Pin layout level, the motor (as well as parts of the internal electronics)
is switched off.

RST: This pin must also be HIGH during operation. A LOW signal turns off the motor
and resets the internal position.

M0, M1, M2: The signals at these three inputs M0 M1 M2 Resolution


determine the step resolution of the motor. In
addition to full- and half- step operation, you can L L L full-step
also select quarter-, eighth-, sixteenth-, and even H L L half-step
thirty-second-step operation on this controller. If
L H L quarter-step
there’s no reason not to, I recommend using the
highest resolution, that is, setting all three lines to H H L eighth-step
HIGH. That way, we don’t need an Arduino output L L H 1/16-step
here either, but can connect these three lines
H H H 1/32-step
permanently to VCC.

186

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 183 2022-07-27 20:20


Chapter 9 • Controlling motors

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.

2A, 2B: The second coil of the motor is connected here.

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.

Adjusting the current


Before we can use the motor control, we have to set the current on this version as well.
Here, the two identical resistors for current measurement are right next to the
potentiometer. Printed values R100 mean 0.1 ohms each, because R stands for the
decimal point. But, there can also be different versions of the board. Therefore, we
should always check first and calculate using the correct value.

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=5IR

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 184 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

9.2.5 Version A4988 vs. DRV8825


The main difference between these two controllers is the number of maximum possible
microsteps. While the A4988 can go down to sixteenths of a steps, the DRV8825 also
allows thirty-second steps, although this does not necessarily mean higher accuracy. If
you don’t need the highest resolution of 32 microsteps, it’s better to use the A4988
board. It can only do sixteenth microsteps, but is generally considered to be more
precise, and the step size is more uniform.

9.3 Brushless motors


In many other motors, the coils rotate and are supplied with current via sliding
contacts. For this purpose, carbon rods (so-called carbon brushes) are usually used,
which have to be replaced after a few years in many conventional motors.

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.

Fig. 9.3: Three different brushless motors

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 185 2022-07-27 20:20


Chapter 9 • Controlling motors

9.3.1 Control using ESC


This difficult task is taken over by a so-
called ESC (electronic speed control), a
module with a lot of electronics. Such
ESCs contain three half-H-bridges (i.e.
one H-bridge for three lines), with which
up to 10, 20, 30, or 40 A can be
switched, plus a dedicated microcon-
troller (a kind of Arduino) on which an
internal motor control program runs.

This all sounds very expensive, but you


can get one straight from China for as
little as 4 euro. You can also search
specifically for “SimonK.” This name
refers to special firmware, a control
program that works with a high switch-
ing frequency, reacts quickly, and is
therefore very popular.
Fig. 9.3.1a: ESC for up to 30 A
with SimonK Firmware

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 186 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Sketch: ESC-control using potentiometer 9_3_1.ino

#define signal_pin 2
#define pot_pin A0

byte power; // motor power (0 to 255)


unsigned int pot_16; // measured value (16 bit)
byte n; // count variable

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 187 2022-07-27 20:20


Chapter 9 • Controlling motors

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:

Fig. 9.3.1b: Controlling an ESC using the Arduino

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 188 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Fig. 9.4: Simple SG90 servo and arm

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 189 2022-07-27 20:20


Chapter 10 • Sensors

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.

Fig. 10: Different sensors

10.1 Analog sensors


The simplest sensors are those whose signal can be measured analogously. For this we
need nothing more than an Arduino’s analog input and possibly a resistor. We have to
distinguish between two types. One of them provides us directly with a voltage that
corresponds to the measured value. There, we should use the Arduino's internal
reference voltage to measure. The others behave like a resistor, and their resistance
value changes analogously with the measured value. There we need (as already briefly
explained in chapter 6.2.5) a second resistor, from which we make a voltage divider
using the operating voltage (VCC). This way, we can use VCC as a reference when
measuring.

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.

10.1.1 Brightness sensor using LDR


LDRs are very simple low-cost light sensors, light-
sensitive resistors that can be used to measure
brightness over a wide range. The brighter the envi-
ronment, the lower their resistance, and in the dark
Fig. 10.1.1: LDR
they become high resistance, up to their maximum in
complete darkness.

193

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 190 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

However, they are usually not very precise (not exactly


standardized). In addition, they are very sluggish – especially
when changing to the very dark. It takes some time until they
reach their highest impedance value. They react faster to high
brightness, but they are not suitable for measuring short light
pulses.

Nevertheless, LDRs more than quite adequate for many light


measurements, e.g. if you just want to check whether it is too
dark at home, so that the Arduino automatically turns on the
Fig. 10.1.1: light. We don't have to worry about a higher tolerance, because
Measuring using LDR we can adjust the threshold value (from which brightness
should be switched) later.

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.

10.1.2 NTC temperature measurement


An NTC is a resistor whose resistance value decreases
with increasing temperature. Therefore, it is also  Fig. 10.1.2: Thermistor (NTC)
referred to as a thermistor. NTCs (unlike LDRs) are often available as a very precise
version with a small tolerance and a precisely defined curve. This allows us to measure
temperature to within a few tenths of a degree without calibration. They are often
available in many different resistance values, with the specified nominal value usually
referring to a temperature of 25 degrees.

Again, in principle it would not matter which version (or how


many ohms at 25 degrees) we use, because we can adapt the
voltage divider, for example with the same number of ohms
as the fixed resistor in the voltage divider. But there is
another aspect to consider here: A voltage divider with too
low an impedance would cause a corresponding current to
flow, which would already minimally heat up the NTC itself,
and that doesn't really fit our intention of measuring the
temperature. I therefore like to use relatively high-impedance
Fig. 10.1.2:
NTCs, for example of 50 kΩ.
Measuring using NTC
194

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 191 2022-07-27 20:20


Chapter 10 • Sensors

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.

Sketch: Temperature measurement using NTC 10_1_2.ino


#define measure_pin A0

unsigned int amount = 10000; // total number of measurements (max. 65535)


float r_ntc25 = 50000; // nominal resistance (in ohms) of the NTC at 25 degrees
float r_fixed = 47000; // fixed resistor (example 47 kiloohms)
float r_factor[33] = // R-factor at -30 to 130°C (steps of 5°) NTC version 3950
{17.3, 12.8, 9.59, 7.25, 5.51, 4.23, 3.27, 2.54, 1.99, 1.57, 1.25, 1,
0.81, 0.656, 0.535, 0.438, 0.36, 0.3, 0.25, 0.21, 0.176, 0.148, 0.126,
0.107, 0.091, 0.0779, 0.0671, 0.0585, 0.0507, 0.0441, 0.0385, 0.0334, 0.0294};

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 192 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

unsigned int counter = 0; // counts measurements


unsigned long sum = 0; // summed individual measurements
float ratio; // resistor ratio
float rm; // measured NTC resistance
float present_factor; // measured NTC factor (1 at 25 degrees)
float temperature; // measured temperature
byte n; // position in r_factor array

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.

present_factor = rm / r_ntc25; // actual measured factor (1 corresponds to 25°C)


if (present_factor > r_factor[0])

196

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 193 2022-07-27 20:20


Chapter 10 • Sensors

{
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).

Serial.println(""); // blank line


Serial.print("NTC: ");
if (rm >= 1000) // if at least 1 kiloohm
{
Serial.print(rm / 1000); // display in kiloohms
Serial.print(" kΩ");
}
else // if under 1 kiloohm
{
Serial.print(rm); // display in ohms
Serial.print(" Ω");
}
Serial.print(" Factor: ");
Serial.print(present_factor); // factor (value 1 at 25 degrees)

197

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 194 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Serial.print(" Temperature: ");


Serial.print(temperature); // measured temperature
Serial.println(" °C"); // degrees Celsius
}
// Reset everything for next count:
counter = 0; // 0 measurements
sum = 0; // no measured values yet
}
}

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.

10.1.3 Analog joystick


Now, a simple potentiometer is basically just a
sensor – one that detects the position of a
rotatable axis and delivers it as an analog
value. A special version that can do this in two
dimensions is the analog joystick. You can get
small boards straight from China with an
analog joystick for less than one euro, which
are worth looking at here.

Fig. 10.1.3a: Analog joystick

To connect to the Arduino, we need


to connect the grounds to each
other and the positive wire to the
Fig. 10.1.3b: Connecting the analog joystick positive pin (VCC) on the Arduino
board.

198

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 195 2022-07-27 20:20


Chapter 10 • Sensors

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.

10.1.4 Measuring light with a photodiode


Photodiodes can measure brightness in a similar way to
LDRs, but they are much faster and can detect even short
pulses of light perfectly. These are basically tiny solar cells
– yes, they actually generate a small voltage when light
hits them, and if we put a little bit of a load on it, it's
perfect for showing us the momentary incidence of light. Fig. 10.1.4a: BPW34

A cheap, much used photodiode is the BPW34. The picture


on the right shows how we use it. RL serves as load
resistor. Depending on the size (e.g. 10 kΩ) we can choose
the brightness range we want to measure – the lower the
resistance, the more light is needed. As reference, we have
to choose the internal 1.1 volts (even if the photodiode Fig. 10.1.4b: Using the
does not quite reach this value). BPW34

10.2 Digital measurements


Most sensors provide a digital output. Often such sensors are already small
“computers” themselves, which pass on their measurement results via a
serial interface. Sometimes, however, it is simply a simple on/off signal that
the sensor registers and passes on, as in the case of the next component:

10.2.1 TL1838 or VS1838B infrared receiver


This component (or a very similar one) can be found in any device that has
an infrared remote control. It’s the receiver that detects the infrared signal
from the remote control and converts it back into a simple digital signal.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 196 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

If you want to make it especially


perfect, you can filter the operating
voltage for the infrared receiver, as
shown in the schematic on the right,
but this is not really necessary,
because we already have a quite clean
voltage with VCC. The receiver
normally works fine directly on VCC Fig. 10.2.1b: Connecting the
(without the 3 additional components). VS1838B

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 197 2022-07-27 20:20


Chapter 10 • Sensors

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.

10.2.2 HC-SR04 ultrasonic distance sensor


With the HC-SR04 our Arduino becomes a bat and can locate distances by using
ultrasound. With several of these modules aligned in different directions, a self-driving
model car could, for example, detect the distance to walls or other obstacles.

Fig. 10.2.2: HC-SR04 ultrasonic distance sensor – front and rear

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 198 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

In detail, the measurement works like this: Whenever the


signal sent to the sensor’s Trig pin changes from HIGH to LOW
(i.e. when a HIGH pulse ends), the sensor generates a short
40 kHz ultrasonic signal with 200 µs duration after 250 µs.
(Strictly speaking, there are only 8 pulses at 25 µs intervals,
which corresponds to a 40 kHz signal with a 200 µs duration).
Then the sensor sends a HIGH signal to the Echo output until
the echo is received and then it switches back to LOW. If the
Fig 10.2.2:
sensor does not receive an echo, the output does not change
Connecting the HC-
back to LOW until 200 ms later. (This time is far beyond all
SR04
measurable distances).

To measure the echo time we use a new function here:

Syntax: pulseIn() function

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.

Sketch: Ultrasonic distance measurement 10_2_2.ino

#define trig_pin 2 // signal to the sensor


#define echo_pin 3 // signal from the sensor

int echo_time; // time to echo in µs


float distance; // distance in cm

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 199 2022-07-27 20:20


Chapter 10 • Sensors

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 200 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

else // if echo time is valid


{
distance = echo_time / 58.275; // conversion (from µs to cm / 2)
Serial.print("Distance: "); // output
if (distance < 100) // if under 1 meter
{
Serial.print(distance, 1); // direct output (with one decimal place)
Serial.println(" cm"); // in cm
}
else // 1 meter upwards
{
Serial.print(distance/100, 2); // output divided by 100 (with 2 decimal places)
Serial.println(" m"); // in meter
}
}
delay(1000); // wait a second
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 201 2022-07-27 20:20


Chapter 10 • Sensors

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.

10.2.3 HC-SR501 motion sensor


Motion detectors are used in many ways today, e.g. to switch on the lighting in the
garden as soon as someone approaches the front door. They react to changes in
thermal radiation, which is the radio frequency range below infrared light.

Fig. 10.2.3a: HC-SR501 motion sensor

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 202 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Fig. 10.2.3b: Directly-connected vs. inverted HC-SR501

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.

Using the two potentiometers, we can adjust


the switch-on duration and the sensitivity.
There are also three pins and a jumper that
connects two of these pins. You can plug it in
so that the motion detector always triggers
only once and basically switches off after the
set time has elapsed, or so that the time
starts over with every movement, so that the
motion detector extends the time with every
movement and switches off only when
nothing moves for a while. So, depending on
Fig. 10.2.3c: HC-SR501 the application, we should think about how
connections and adjustments to plug the jumper.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 203 2022-07-27 20:20


Chapter 10 • Sensors

Supplying power to the HC-SR501


We can connect the VCC connectors from the motion detector and the Arduino directly
together. The HC-SR501 prefers a clean operating voltage. Especially if the sensitivity is
set high, or false positives may occur – movements may be detected where there are
none. Maybe an additional electrolytic capacitor (100 µF) from VCC to GND, or a small
shielding plate around the board, which then has to be connected to the negative
terminal (GND), can help. Otherwise it helps to turn down the sensitivity.

According to the data sheet, the HC-SR501 requires a voltage


of at least 4.5 volts and a maximum of 20 volts. However,
experience shows that the motion detector also works well with
lower voltages, for example with a lithium-ion battery. On the
HC-SR501’s PCB, the VCC pin leads through a diode to a 3.3 V
voltage regulator that powers the actual circuitry. Since this
diode has no other purpose than to protect against wrong
polarity, we can safely bypass it. The circuit then copes even
better with low battery voltage.

10.2.4 The I²C interface


The I²C interface (pronounced "I-squared-C", often also called I2C, IIC or TWI) is a
widely-used standard for serial communication between microcontrollers and other
devices. Many sensors use this interface. The big advantage is that for the entire
communication (for example including several sensors) only two lines are ever needed.

SCL and SDA


SCL is clock and SDA is data – these are the two lines of the I²C interface. To prevent
the devices from short-circuiting each other (e.g. by one device sending a HIGH signal
and the other simultaneously sending a LOW signal to the same line), the devices
operate according to the open collector principle.

207

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 204 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Fig. 10.2.4a: Bidirectional data line using open collectors

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 205 2022-07-27 20:20


Chapter 10 • Sensors

I²C on the ATmega328 and 168


This way the Arduino can now communicate with
multiple sensors and needs only 2 signal lines. On the
ATmega328 (and 168) it is pins A4 (SDA) and A5 (SCL)
which are intended for I²C communication. The library
Wire.h, which we include for this, provides us with the
necessary functions.
Fig. 10.2.4b: I²C on Arduino
The VCC line is dashed in the picture because the
devices do not necessarily have to use the same power supply. Even if the Arduino is
operated with 5 volts and the sensor with 3.3 volts, this is usually not a problem.

Syntax: Including libraries


#include <Wire.h> //I2C Arduino Library

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 206 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

And this is what we can do with this library:

Syntax: I²C functions with Wire.h


#include <Wire.h> // I2C Arduino Library
#define address 0x77 // I2C 7-bit address for the BMP180

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.

Wire.beginTransmission(address); // Begin communication with BMP180 sensor


Wire.write(0xF4); // Select Control Register F4
Wire.write(0x2E); // Write value 0x2E (meaning temperature measurement)
Wire.endTransmission();

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).

Wire.beginTransmission(address); // begin communication with BMP180


Wire.write(0xF6); // select register for temperature result
Wire.endTransmission();

Now we have again accessed a register (0xF6), but without writing anything into it,
because we now read this register (and the following ones).

Wire.requestFrom(address,2); // fetch 2 bytes (temperature integer value


while(!Wire.available()); // wait
int value = Wire.read()<<8; // most-significant byte comes first
while(!Wire.available()); // wait
value |= Wire.read(); // least-significant byte thereafter

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 207 2022-07-27 20:20


Chapter 10 • Sensors

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.

10.2.5 BMP180 air pressure sensor


This small sensor performs amazingly. It
measures air pressure with incredible
accuracy. With this 2-euro part, we can
measure the difference between the air
pressure on the desk and on the floor. On
calm days (with consistent air pressure) a
change in altitude of a few centimeters is
measurable. Fig. 10.2.5a: BMP180 air pressure sensor

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 208 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Project: Pressure and altitude sensing with the BMP180


Now, as an example, we can build ourselves an air pressure sensor and altimeter,
because from the air pressure the height above sea level can be calculated, but bear in
mind that the weather also plays a role, of course. In high-pressure conditions we get a
slightly lower altitude displayed, while under low-pressure conditions, a higher altitude
is displayed. Relative differences in altitude (within a short period of time in the same
weather conditions), on the other hand, are detected quite accurately by the altimeter.

We need:

• 1 board (e.g. Pro Mini) with an ATmega328 or 168 running at 8 or 16 MHz


• 1 air pressure sensor breakout board with a BMP180 sensor
• 1 serial-to-USB adapter (for power supply and for Serial Monitor)
• 4 short wires, e.g. jumpers

212

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 209 2022-07-27 20:20


Chapter 10 • Sensors

In the sketch we now use self-defined functions, rather than just setup() and loop().
Thus, a few explanations first:

Syntax: Function definitions


void switchOff()
{
digitalWrite(pin_a, LOW);
digitalWrite(pin_b, LOW);
digitalWrite(pin_c, LOW);
digitalWrite(pin_d, LOW);
}

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).

So, should you put multiplyBy10(3), your result will be 30.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 210 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Air-pressure sensor and altimeter 10_2_5.ino

#include <Wire.h> // I2C library


#define address 0x77 // I2C 7bit address of the BMP180

char oversampling = 3; // 0-> 1x , 1 -> 2x , 2 -> 4x , 3 -> 8x


int duration[4] = {4500, 7500, 13500, 25500}; // times (µs) depending on
oversampling
int ac1, ac2, ac3, b1, b2, mb, mc, md, n, temperature; // several variables
unsigned int ac4, ac5, ac6, t, next_time;
long x1, x2, x3, b3, b5, b6, p, pressure, altitude;
unsigned long b4, b7;

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

Wire.beginTransmission(address); // select device (BMP180)


Wire.write(0xAA); // position of the calibration data in the BMP180 chip
Wire.endTransmission();

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.

Wire.requestFrom(address,22); // load calibration data (22 bytes or 11 int values)


ac1 = read_int(); // reads the next two bytes in each line
ac2 = read_int();
ac3 = read_int();
ac4 = read_int();
ac5 = read_int();
ac6 = read_int();
b1 = read_int();
b2 = read_int();
mb = read_int();
mc = read_int();
md = read_int();

Serial.begin(38400); // prepare serial transmission

214

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 211 2022-07-27 20:20


Chapter 10 • Sensors

Serial.println("Calibration data:"); // serial output


Serial.println(ac1);
Serial.println(ac2);
Serial.println(ac3);
Serial.println(ac4);
Serial.println(ac5);
Serial.println(ac6);
Serial.println(b1);
Serial.println(b2);
Serial.println(mb);
Serial.println(mc);
Serial.println(md);
}

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

Serial.print("Temp.: "); // display temperature, pressure and altitude


Serial.print(((float)temperature/10), 1);
Serial.print("°C Value: ");
Serial.print(p);
Serial.print(" Pressure: ");
Serial.print(((float)pressure/100), 2);
Serial.print("hPa(mbar) Altitude: ");
Serial.print(((float)altitude/100), 2);
Serial.println("m");
delay(1000);
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 212 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

void get_t() // measure temperature


{
Wire.beginTransmission(address); // address BMP180
Wire.write(0xF4); // select control register
Wire.write(0x2E); // value for temperature measurement
Wire.endTransmission(); // complete action
delayMicroseconds(4500); // wait for result
Wire.beginTransmission(address); // address BMP180
Wire.write(0xF6); // select address for measurement result
Wire.endTransmission(); // complete action
Wire.requestFrom(address,2); // read measured value (16 bit)
t = read_int(); // read 16 bit (2 byte)
}

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.

void get_p() // measure pressure


{
Wire.beginTransmission(address); // address BMP180
Wire.write(0xF4); // select control register
Wire.write(0x34 + (oversampling<<6)); // measure pressure (depends on setting)
Wire.endTransmission(); // complete action
next_time = micros() + duration[oversampling]; // define end of waiting time
while (32760 & (next_time - micros())); // wait duration
Wire.beginTransmission(address); // address BMP180
Wire.write(0xF6); // select address for measurement result
Wire.endTransmission(); // complete action
Wire.requestFrom(address,3); // read measured value (24 bit)
while(!Wire.available()) {} // wait
p = (long)Wire.read()<<16; // first byte
p |= read_int(); // second and third byte
p >>= (8-oversampling); // 16 - 19 bit resolution
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 213 2022-07-27 20:20


Chapter 10 • Sensors

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 214 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

The output window


If we start the air pressure and altimeter, the calibration values are displayed first. Then
the actual output takes place approximately every second:

Fig. 10.2.5b: Output in the Arduino IDE’s Serial Monitor

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 215 2022-07-27 20:20


Chapter 10 • Sensors

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.

Accuracy from double-oversampling


“Oversampling” is the term used when more measurements are taken than are actually
needed. If you then use the average of all measurements, the result is more accurate,
i.e. the noise is lower, than with a single measurement. The BMP180 can already do this
internally. With the value 3 in the “oversampling” variable we have already specified
that each output should consist of 8 measurements. Nevertheless, the measurement
takes only a few milliseconds, although we output the value only once per second.

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.

10.2.6 MPU-6050 accelerometer


The MPU-6050 is another example of a typical sensor with
an I²C interface. It can measure rotational and
acceleration forces with high precision in three dimensions.
In its rest position, for example, it measures only
gravitational acceleration, i.e. the force of gravity, and
depending on the position, this force is distributed
differently on the three spatial axes, X, Y, and Z of the
sensor, from which it is then possible to calculate, for
example, which way up and down are.

The application possibilities are endless. You could build an


electronic spirit level with it, or measure vibration.
Pedometers use such a sensor and evaluate the
movements to count steps. High-quality drones know how
they are moving thanks to such a sensor, and our
Fig. 10.2.6 Accelerometer
smartphone knows where up and down is at all times and
breakout board
can automatically rotate its display accordingly.

219

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 216 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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:

• 1 board (e.g. Pro Mini) with an ATmega328 or 168 at 8 or 16 MHz


• 1 sensor breakout board with MPU-6050 gyro and accelerometer
• 1 serial-to-USB adapter (for power supply and Serial Monitor output)
• 4 short wires, e.g. jumpers

Sketch: Rotation and acceleration measurement 10_2_6.ino

#include<Wire.h>
#define address 0x68 // I2C 7bit address of the MPU-6050

byte ac_range = 3; // acceleration range 0: ±2g, 1: ±4g, 2: ±8g, 3: ±16g


byte gy_range = 3; // Rotation range 0: ±250, 1: ±500, 2: ±1000, 3: ±2000°/s

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)

Wire.beginTransmission(address); // address the MPU-6050


Wire.write(0x1B); // pointer to register 0x1B
Wire.write(gy_range << 3); // rotation range to register 0x1B
Wire.write(ac_range << 3); // acceleration range to register 0x1C
Wire.endTransmission(true);

220

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 217 2022-07-27 20:20


Chapter 10 • Sensors

Wire.beginTransmission(address); // address the MPU-6050


Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // activate sensor
Wire.endTransmission(true);

Serial.begin(38400); // prepare serial transmission


}

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).

ac_x = accel_x * 2.0 / (32768 >> ac_range); // convert X acceleration to g


ac_y = accel_y * 2.0 / (32768 >> ac_range); // convert Y acceleration to g
ac_z = accel_z * 2.0 / (32768 >> ac_range); // convert Z acceleration to g
temperature = (temp / 340.0) + 36.53; // convert temperature value to °C
gy_x = gyro_x * 250.0 / (32768 >> gy_range); // X rotation in degrees per second
gy_y = gyro_y * 250.0 / (32768 >> gy_range); // Y rotation in degrees per second
gy_z = gyro_z * 250.0 / (32768 >> gy_range); // Z rotation in degrees per second

221

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 218 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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).

Serial.print("\nX-Acceleration: "); Serial.print(accel_x); // display values


Serial.print(" "); Serial.print(ac_x); Serial.print(" g ");
Serial.print(ac_x * 9.81); Serial.println(" m/s²");

Serial.print("Y-Acceleration: "); Serial.print(accel_y);


Serial.print(" "); Serial.print(ac_y); Serial.print(" g ");
Serial.print(ac_y * 9.81); Serial.println(" m/s²");

Serial.print("Z-Acceleration: "); Serial.print(accel_z);


Serial.print(" "); Serial.print(ac_z); Serial.print(" g ");
Serial.print(ac_z * 9.81); Serial.println(" m/s²");

Serial.print("\nTemperature: "); Serial.print(temperature); Serial.print(" °C\n\n");

Serial.print("X-Rotation: "); Serial.print(gyro_x);


Serial.print(" "); Serial.print(gy_x); Serial.println(" °/s");

Serial.print("Y-Rotation: "); Serial.print(gyro_y);


Serial.print(" "); Serial.print(gy_y); Serial.println(" °/s");

Serial.print("Z-Rotation: "); Serial.print(gyro_z);


Serial.print(" "); Serial.print(gy_z); Serial.println(" °/s");

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 219 2022-07-27 20:20


Chapter 10 • Sensors

other. The value 1 should only come


out if 2 of the values are 0. Other-
wise, the sum is always somewhat
larger. For example, at a 45-degree
angle (when one value is 0 and the
other two are equal), the two val-
ues should be about 0.7 g each and
would be 1.4 together (the square
root of 2). To add the “g” values
correctly, we must add the individ-
ual values squared and take the
root from the sum. However, the
measured values always have a few
percent tolerance. Fig. 10.2.6b: MPU-6050 output

10.2.7 HMC5883L magnetic field sensor


This sensor can also measure spatially in three
dimensions. In this case, however, the focus is on
magnetic fields. The orientation of the earth’s
magnetic field can be measured with it, for exam-
ple. Even small magnets in the vicinity can be
measured well, and strong magnets even from a
Fig. 10.2.7: Magnetic field sensor
distance of several meters.

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:

• 1 board (e.g. Pro Mini) with ATmega328 or 168 at 8 or 16 MHz


• 1 HMC5883L magnetic field sensor breakout board
• 1 serial-to-USB adapter for power supply and Serial Monitor output
• 4 short wires, e.g. jumpers

223

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 220 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: 3D compass 10_2_7.ino

#include <Wire.h> //Integrate I2C functions


#define address 0x1E //0011110b, I2C 7-bit address of the HMC5883L

int x_raw, y_raw, z_raw; // measured values


long x_added, y_added, z_added; // Measured values 64 times added up
float x, y, z, angle, dip; // converted values
byte n; // count byte

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

Wire.beginTransmission(address); // address the HMC5883L


Wire.write(0x00); // pointer to register 0 (Configuration Register A)
Wire.write(B00011000); // Set to 75 measurements per second

// Register 1 (Configuration Register B):


Wire.write(B00000000); // highest sensitivity, factor: 0.0737 µT
//Wire.write(B01100000); // medium sensitivity, factor: 0.152 µT
//Wire.write(B11100000); // lowest sensitivity, factor: 0.435 µT

Wire.write(0x00); // set mode Register (2) to continuous measurement


Wire.endTransmission();
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 221 2022-07-27 20:20


Chapter 10 • Sensors

void loop()
{
x_added = 0; y_added = 0; z_added = 0; // reset

for (n=0; n<64; n++)


{
delay(13); // 75 measurements per second

Wire.beginTransmission(address); // address the HMC5883L


Wire.write(0x03); // pointer to register 3
Wire.endTransmission();

Wire.requestFrom(address, 6); // request 6 bytes (3 int values)


while(Wire.available() < 6); // if necessary, wait till data is available
x_raw = Wire.read()<<8 | Wire.read(); // X value
z_raw = Wire.read()<<8 | Wire.read(); // Z value
y_raw = Wire.read()<<8 | Wire.read(); // Y value

x_added += x_raw; // sum up


y_added += y_raw;
z_added += z_raw;
}

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.

Such a summed up multiple measurement increases accuracy and is always


recommended if you need the measured values only at longer time intervals (e.g. once
per second) and if there is nothing else to do in any case.

225

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 222 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

x = x_added * 0.0737 / 64; // conversion from raw value to µT (microtesla)


y = y_added * 0.0737 / 64;
z = z_added * 0.0737 / 64;

angle = atan2(-y, x) * 180 / M_PI; // horizontal angle of the magnetic field


if (angle < 0) angle += 360; // range +/- 180° -> 0 to 360°
dip = atan2(z, sqrt((x * x) + (y * y))) * 180 / M_PI; // vertical angle

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 223 2022-07-27 20:20


Chapter 10 • Sensors

Output window

Fig. 10.2.7b: 3D compass serial output

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.

10.2.8 GY-87 multi-sensor


Since several devices can be connected to an I²C
interface simultaneously, the individual sensors,
BMP180 (for air pressure), MPU-6050 (for acceleration
and rotation), and HMC5883L (for magnetic fields) can
be acquired together on a single breakout board.
Called the GY-87, it’s a small board with these three
sensor chips on it. You don't necessarily save money
with this, because the GY-87 board costs about the
same as the three individual sensor boards, but, if you
want to use all three chips anyway, it is of course
convenient to only have to connect one sensor board
and to be able to do everything with it.

Fig. 10.2.8: GY-87
multi-sensor breakout board

227

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 224 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Chapter 11 • Other components


Apart from sensors, there are countless other small boards and modules that have been
designed to communicate with the Arduino. We’ve already met some control elements,
such as USB adapters, transistor arrays, solid-state relays, H-bridges, stepper motor
controllers and ESCs, but there’s a lot more to discover:

11.1 RF remote control


In section 13.7, we’ll learn about an infrared receiver that can work with just about any
chosen infrared remote control. For short distances, where there’s a clear line of sight to
the receiver, an infrared remote control is a great choice, but sometimes we need a
greater range or a remote that can work through walls—regardless of position.

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.

Fig. 11.1a: 4-button RF remote control (2262/2272) and receiver board

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 225 2022-07-27 20:20


Chapter 11 • Other components

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 226 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

11.2 Seven-segment displays


7-segment displays are the simplest way to get the Arduino to display numeric values
without having to connect the serial connection to the PC and view output in Serial
Monitor. There are basically two variants: displays with a common anode (positive
terminal), and displays with a common cathode (negative terminal).

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.

Now, we can drive the 7 segments from 7 outputs


on the Arduino (or 8 for 8 segments, if the decimal
point is to be used as well), via 330-ohm resistors. If
the display has a common anode, this is connected
to positive (VCC). A common cathode, on the other
hand, must be connected to ground (GND). We must
also consider the control: The segments light up on
HIGH for a common cathode, but on LOW for a
common anode.

Multiplexing Fig. 11.2c: Direct control

Usually, however, 7-segment displays are connected using a multiplexing method. In


contrast to simple direct control of each segment, as in the previous example, the
multiplexing method offers several advantages. It allows us to control several digits,
and we don’t need 7 additional Arduino outputs for each one, but rather only 7 pins plus

230

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 227 2022-07-27 20:20


Chapter 11 • Other components

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.

So that each segment can


be controlled individually,
the segments must not
light up at the same time,
but are clocked one after
the other a hundred times
per second (or even faster).
Fig. 11.2d: Multiplexed control of four 7-segment digits Only the segments that get
a HIGH signal from the Ar-
duino while the common
cathode is LOW (via a resis-
tor) at same time light up.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 228 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Often you can find cheap multiple-segment


displays in the trade, which already contain 2,
3, or 4 digits. I use these happily, because one
saves much work with the wiring. The single 7
or 8 segments are usually already connected
internally between the single digits. The com-
mon anode or common cathode, on the other
Fig. 11.2e: 4-digit display
hand, is available separately for each digit,
with decimal points and colon
just as we need it.

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.

11.2.1 Basic program for 1 to 6 digits


With this basic sketch, we can control 7-segment displays with 1 to 6 digits (more if
required). The basic requirement is a pin assignment as shown earlier in Fig. 11.2d.
That means: Each segment, “a” to “g,” as well as the dots, must be connected to the
corresponding segments of all digits. Furthermore, you need we have one common

232

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 229 2022-07-27 20:20


Chapter 11 • Other components

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.

Sketch: 7-segment display with several digits 11_2_1_a.ino

byte digits = 4; // number of digits


boolean common_anode = false; // false with common cathode
byte segment_pin[8] = {11,A1,6,8,9,12,5,7}; // a,b,c,d,e,f,g,point
byte digit_pin[6] = {4,A0,13,10}; // last to first digit

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.

int frequency = 200; // frequency for display in Hz (about 100 to 300)


float change_frequency = 2; // update display 2 times per second
byte segments[20] = // bit pattern (a to g) for the different digits
{
B1111110, B0110000, B1101101, B1111001, B0110011, // 0,1,2,3,4
B1011011, B1011111, B1110000, B1111111, B1111011, // 5,6,7,8,9
B1110111, B0011111, B1001110, B0111101, B1001111, // A,b,C,d,E
B1000111, B0000001, B1100011, B0000000, B0000000 // F,-,°, ,
};
byte digit[4] = {4,3,2,1}; // values of the single digits (1, 2, 3 and 4)
byte dot_pos = 0; // point position 1 to 4 (0 -> no point)

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 230 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

int loop_time; // microseconds per loop pass


int next_time = 0; // next loop time (in microseconds)
byte segment = 0; // actual segment
unsigned int counter = 0; // counts loop passes
byte n; // counts actual display position

Then follow the other variables that will be used later.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 231 2022-07-27 20:20


Chapter 11 • Other components

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.

digitalWrite(segment_pin[segment], common_anode); // actual segment off


segment++; // next segment
segment &= 7; // only last 3 bits (0 to 7)
for (n=0; n<digits; n++) // for all display digits (e.g. 0 to 3)
{
if (segment < 7) // for digit segment (not for point)
{
// Switch the actual segment of this position depending if it should be on or off:
digitalWrite(digit_pin[n], (common_anode != !(segments[digit[n]] & (64 >>
segment))));
}
else // for point segment
{
digitalWrite(digit_pin[n], (dot_pos == (n + 1)) == common_anode); // point
}
}
digitalWrite(segment_pin[segment], !common_anode); // actual segment on

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 232 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

if (counter >= (frequency << 3) / change_frequency) // if change time reached


{
// This part is called according to change_frequency
// (e.g. 2 times per second). Here we can e.g. update
// digit[0 to 3] the displayed value or call functions that do this.
counter = 0; // reset
}
else // for all other passes
{
// This part is called otherwise at each pass,
// frequency * 8 (i.e. over 1000) times per second.
// Here we can e.g. accommodate measurements and other parts.
counter++; // counting for redisplay
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 233 2022-07-27 20:20


Chapter 11 • Other components

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 Ω.

The optimal display


From China, you can find many offers for these very cheap
4-digit 7-segment displays that are perfect for connecting
to the Pro Mini. They’re available in different sizes. It’s best
to use the 0.56-inch version, because there the two rows of
connectors have the optimal distance, matching inputs 4 to
A1 on the Pro Mini. This way, we can solder the display Fig. 11.2.1a: 4-digit
directly to the board and don't need any connecting wires. 7-segment display

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 234 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

This picture shows the pinout of the


common 4-digit version viewed from
behind. You can tell which side faces
down from the decimal points on the
front. The pin assignment is the same
for all sizes. However, the two pin
rows are spaced at different distances
Fig. 11.2.1c: Pin layout for a 4×7-segment apart, depending on the size of the
display.

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:

Adaptations for 2- to 4-digit displays


byte digits = 2; // number of digits
boolean common_anode = false; // false with common cathode
byte segment_pin[8] = {13,12,7,8,9,10,6,5}; // a,b,c,d,e,f,g,point
byte digit_pin[6] = {A0,11}; // last to first digit
byte digits = 3; // number of digits
boolean common_anode = false; // false with common cathode
byte segment_pin[8] = {11,A1,6,8,9,12,5,7}; // a,b,c,d,e,f,g,point
byte digit_pin[6] = {A0,13,10}; // last to first digit
byte digits = 4; // number of digits
boolean common_anode = false; // false with common cathode
byte segment_pin[8] = {11,A1,6,8,9,12,5,7}; // a,b,c,d,e,f,g,point
byte digit_pin[6] = {4,A0,13,10}; // last to first digit

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 235 2022-07-27 20:20


Chapter 11 • Other components

int, float, hex, and degree displays


So far, we can only assign a display value to the individual digits with our program via
the digit[] array. To display numbers (for example measured values) directly, we can
add the following functions to our sketch.

Here, we make use of the modulo operator in our calculations, for the first time. I will
explain it briefly:

Syntax: modulo operator


a = 25 % 7;

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.

Sketch: 7-segment display functions 11_2_1_b.ino

void displayInt(int value) // display integer numbers


{
dot_pos = 0; // no point
bool negative = false;
if (value < 0) // if number is negative
{
negative = true; // mark as negative
value = -value; // positive number
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 236 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

byte pos = 0; // display position


while (pos < digits - negative && (value || !pos))
{ // as long as there are more digits to display
digit[pos] = value % 10; // show last digit of value
value /= 10; // divide by 10 (remove last digit)
pos++; // next display digit before
}
if (negative) // for negative value
{
digit[pos] = 16; // minus sign in front
}
if (value) // if number is too large (or negative and too small)
{
while(pos) // as long as the last digit has not been reached yet
{
pos--; // next display digit behind
digit[pos] = 14; // "E" character for Error
}
}
else // if number in permitted range
{
pos += negative; // consider possibly minus sign
while (pos < digits) // if first position has not been reached yet
{
digit[pos] = 19; // fill with spaces in front
pos++; // next digit before
}
}
}

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.)

void displayFloat(float value) // display decimal numbers


{
bool negative = false;
if (value < 0) // if number is negative
{
negative = true; // mark as negative

240

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 237 2022-07-27 20:20


Chapter 11 • Other components

value = -value; // positive number


}
byte extent = 1 + negative; // required digits
float i = 10; // comparative value
while (value >= i) // as long as further digit required
{
extent++; // one digit more
i *= 10; // increase comparison value by one decimal place
}
dot_pos = 1; // Point at the end (as long as no decimal places)
while (extent < digits) // as long as there is space for (another) decimal digit
{
value *= 10; // increase by one decimal place
dot_pos++; // move point by one position
extent++; // use one more digit
}
byte pos = 0; // writing position (last digit)
int val = round(value); // continue with integer (rounded)
if (extent > digits) // if digits don't reach
{
val = 0; // show no value
while (pos < digits - negative) // till the (second)foremost digit
{
digit[pos] = 14; // show "E" for Error
pos++; // next digit before
}
}
while (pos <= dot_pos - 1 || val) // as long as further digit needed
{
digit[pos] = val % 10; // show digit
val /= 10; // divide by 10 (remove last digit)
pos++; // to the next digit before
}
if (negative) digit[digits-1] = 16; // minus sign in front, if negative
if (dot_pos == 1) dot_pos = 0; // don't display point if no decimal digits
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 238 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

void displayHex(unsigned long value) // display hexadecimal number


{
dot_pos = 0; // show no point
for (byte pos=0; pos<digits; pos++) // last to first digit
{
digit[pos] = value & 15; // show last 4 bits
value >>= 4; // shift by 4 bits (last 4 out)
}
if (value) // if digits are not enough
{
for (byte pos=0; pos<digits; pos++) // for all digits
{
digit[pos] = 16; // "-" (show dash)
}
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 239 2022-07-27 20:20


Chapter 11 • Other components

else if (extent > digits) // if digits are not enough


{
value = 0; // show no value
pos = 0; // from the last digit ...
while (pos < digits - negative) // ... to the (second) foremost digit
{
digit[pos] = 14; // show "E" for Error
pos++; // next digit before
}
}
int val = round(value); // continue with (rounded) integer
while (val || (pos < digits - negative && pos < dot_pos))
{ // as long as further digit needed
digit[pos] = val % 10; // show digit
val /= 10; // divide by 10 (remove last digit)
pos++; // to the next digit before
}
if (negative) digit[pos] = 16; // minus sign in front if negative
else if (pos < digits) digit[pos] = 19; // space if digit not used
if (dot_pos < 3) dot_pos = 0; // no point if integer
}

The displayDegree() function is similar to the displayFloat(), but degree symbol ° is


shown here at the last position of the display. In addition, only a maximum of one
decimal place is displayed, provided there is space for it. The function is therefore very
suitable for displaying temperatures.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 240 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

displayDegree() is perfect for displaying temperatures (or angles, also given in


degrees). In the last position, degree sign ° is always displayed. On the remaining
digits, the float value is displayed, similar to displayFloat, but with a maximum of one
digit after the decimal point. Thus, temperatures from -99° to 999° can be displayed on
a 4-digit display, or from -9.9° to 99.9° with one decimal place. In case of overflow, all
digits show an E, and in case of negative overflow, with a minus sign in the first
position. We can use 3- and 4-digit displays.

11.2.2 Project: Voltmeter


We need:
For this project, besides the display,
we only need two additional resistors • ATmega168 (or 328) Pro Mini board, 8 MHz
to serve as a voltage divider and we • 4-digit 7-segment display, 0.56-inch
can build a 4-digit voltmeter. We can • 4 resistors, 100 Ω / ¼ W
freely select the measuring range by • 2 resistors. See table on page 72
selecting the voltage divider.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 241 2022-07-27 20:20


Chapter 11 • Other components

Sketch: Voltmeter with 7-segment display 11_2_2.ino

#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!)

byte digits = 4; // number of digits


boolean common_anode = false; // false with common cathode
byte segment_pin[8] = {11,A1,6,8,9,12,5,7}; // a,b,c,d,e,f,g,point
byte digit_pin[6] = {4,A0,13,10}; // last to first digit
[...]

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.

unsigned int measurement; // actual measured value


unsigned long measurements = 0; // summed measured values
float conversion_factor; // conversion factor to millivolt
float voltage; // Voltage in millivolts
unsigned int clipped = 0; // counts (possibly overdriven) maximum values
[...]

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.

conversion_factor = (float(reference) / 1024); // reference and converter range


conversion_factor *= 1 + (r1_resistor / gnd_resistor); // and voltage divider
[...]

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 242 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

if (counter >= (frequency << 3) / change_frequency) // if time is reached


{
voltage = measurements * conversion_factor / counter; // voltage in mV
if (clipped) // if the analog-to-digital converter is possibly overdriven
{
voltage = 999999999; // voltage too high (display "E")
}
displayFloat(voltage/1000); // display voltage in volts
measurements = 0; // reset
counter = 0; // reset
clipped = 0;
}
else // for all other passes
{
measurement = analogRead(measure_pin); // actual measurement
measurements += measurement; // sum measured values
if (measurement == 1023) clipped++; // count overridden values
counter++; // count for measurement and redisplay
}

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.

11.2.3 Project: Thermometer


We need:
Here again we use our basic 7-seg-
ment display sketch – this time with • ATmega168 (or 328) Pro Mini board, 8 MHz
the displayDegree() function and the • 4-digit, 7-segment display, 0.56-inch
modified sketch for temperature mea- • 4 resistors, 100 Ω / ¼ W
surement by means of NTC from • NTC 3950 and resistor. See section 10.1.2
chapter 10.1.2.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 243 2022-07-27 20:20


Chapter 11 • Other components

As shown in the picture on the left, a simple


adapter could look like this, which we can use to
measure temperatures (at input A3) and voltages
with (at input A2) with a voltage divider.

As a thermometer with a degrees display, it looks


like this:

Fig. 11.2.3a: Adapter to measure Fig. 11.2.3b: Temperature display in degrees


temperature and voltage

Sketch: Thermometer with 7-segment display 11_2_3.ino

#define measure_pin A3 // Measuring input (A0 and A1 used for display)

float r_ntc25 = 50000; // resistance of the NTC at 25 degrees


float r_fixed = 47000; // fixed resistor (example 47 kiloohm)
float r_factor[33] = // R-factor at -30 to +130 degrees (steps of 5°) NTC version 3950
{17.3, 12.8, 9.59, 7.25, 5.51, 4.23, 3.27, 2.54, 1.99, 1.57, 1.25, 1,
0.81, 0.656, 0.535, 0.438, 0.36, 0.3, 0.25, 0.21, 0.176, 0.148, 0.126,
0.107, 0.091, 0.0779, 0.0671, 0.0585, 0.0507, 0.0441, 0.0385, 0.0334, 0.0294};

byte digits = 4; // number of digits


boolean common_anode = false; // false for common cathode
byte segment_pin[8] = {11,A1,6,8,9,12,5,7}; // a,b,c,d,e,f,g,point
byte digit_pin[6] = {4,A0,13,10}; // last to first digit

int frequency = 200; // frequency of display in Hz (about 100 to 300)


float change_frequency = 0.5; // refresh display every 2 seconds
[...]

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 244 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

if (counter >= (frequency << 3) / change_frequency) // if time is reached


{
ratio = sum / float((1024 * (long)counter) - sum); // ratio
rm = r_fixed * ratio; // NTC resistor (with NTC against ground)
//rm = r_fixed / ratio; // NTC resistor (for NTC against VCC)
present_factor = rm / r_ntc25; // actual measured factor (1 corresponds to 25°C)
if (present_factor > r_factor[0]) temperature = -99999; // too cold
else if (present_factor < r_factor[32]) temperature = 99999; // too hot
else // valid measured value
{
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]);
}
displayDegree(temperature);
sum = 0; // reset summed value
counter = 0; // reset
}
else // in all other cases
{
sum += analogRead(measure_pin); // sum new measurement
counter++; // counting for measurement and redisplay
}
[...]

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.

If a new measured value is to be displayed, the calculation is performed using the


r_factor[] array, as in sketch 10.1.2 on page 195. If the value is outside the predefined
range, however, no error message is output serially, but the value is set either so high
or so low that the displayDegree() function indicates an overflow. Otherwise, the
temperature is calculated as in sketch 10.1.2. The output is then output to the display
using the displayDegree() function, and then “sum” and “counter” are reset for the
next summation. In the “else” block, the temperature is measured, summed, and the
counter is increased.

248

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 245 2022-07-27 20:20


Chapter 11 • Other components

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 246 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

11.3 Text displays with back-lighting


If a display is to do more than just output a few individual digits, we need a larger one.
These are available in all kinds of versions with different interfaces. With many of them,
you can control the individual pixels directly, but this limits their use with the
ATmega328, because most of the time you (also) want to output text, and this would
stretch the ATmega328 to its limits, given the required character set.

Fig. 11.3a: Blue 1602 display and green 2004 display

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.

The common designations of these


displays are always 4-digit num-
bers, which are derived from the
number of characters per line and
the number of lines. For example,
a 1602 display has 2 lines of 16
characters each. A 2004 display
has, 4 lines of 20 characters each,
accordingly. Also common is the
number 1604 for 4 lines of 16
characters each. Each of these dis-
plays has a back-light, is
adjustable in brightness and is
available in different colors, e.g.
yellow, green, and blue.

Fig. 11.3b: Wiring character displays

250

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 247 2022-07-27 20:20


Chapter 11 • Other components

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.

Pinouts and functions


LED- or K: Negative pole for back-lighting – usually tied to ground.
LED+ or A: Positive pole for back-lighting – depending on display, 3 or 5 volts.
DB0 to DB7: Data bits – for control, we just need the uppermost 4 bits.
E: “Enable” pin for activation.
R/W: Set to HIGH for reading, LOW for writing. May be tied to ground.
RS: Register Select – LOW for Instruction register, HIGH for Data Register
V0: Analog contrast setting – 0 to 5 V. (The optimal value lies somewhere inbetween.)
VDD: Operating voltage – depending on display, 5 volts or 3.3 to 5 volts.
VSS: The display’s ground connection (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 Ω.

For the contrast voltage (0 to


5 volts) the circuit contains a
potentiometer. However, using
pulse-width modulation and
demodulation, the Arduino can
control the contrast directly,
without the need for a poten-
tiometer, as in section 8.2.

Here, V0 is no longer controlled


by a potentiometer, but by a
PWM signal via a low-pass filter.
The contrast can be adjusted in
software. Of course, we have to
use one of the Arduino’s
PWM-capable pins.

Fig. 11.3c: Contrast control using PWM


251

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 248 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Syntax: Controlling text displays


#include <LiquidCrystal.h> // include display functions

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 249 2022-07-27 20:20


Chapter 11 • Other components

Sketch: Example with user-defined characters 11_3.ino


#include <LiquidCrystal.h> // include display functions

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.

// define ohm character (8 bit rows:)


// · · · · ·
// · * * * ·
// * · · · *
// * · · · *
// * · · · *
// · * · * ·
// * * · * *
// · · · · ·
byte ohm[8] = {B00000,B01110,B10001,B10001,B10001,B01010,B11011,B00000};

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 250 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Text display with I²C interface


For the sake of completeness, I want to
mention that there is also an I²C
interface specifically for these text
displays. This can be connected directly
to the display’s socket connector. It only
needs an I²C interface to the Arduino,
so we need fewer pins from the Arduino.
In addition, the potentiometer for the
contrast setting is already on the board.

We do need to install the necessary Fig. 11.4x: I²C-Interface


library separately. See the Arduino page
at arduino.cc/reference/en/libraries/liquidcrystal-i2c for more information.

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).

With a power of max. 5 mW, they’re not really


dangerous. Sure, it’s better not to look directly
into the beam, but it’s also better not to look
directly at a strong power LED or halogen lamp
either, and this would be roughly comparable.
Fig. 11.4: Mini laser modules
You can get versions of these mini lasers in
both 5 volts and 3 volts, the latter of which can naturally also be operated at 3.3 volts.
They often differ often only by the integrated series resistor, which is built-in as an SMD
component. The lens (for a parallel point beam) is also built-in. The light is almost
always red, because laser diodes of other colors are much more expensive.

254

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 251 2022-07-27 20:20


Chapter 11 • Other components

Laser application examples


I present these small lasers here because with a current consumption of a maximum of
40 mA, they are perfectly suitable for control with an Arduino – one output of the
ATmega328 (and 168) can supply just enough current to switch such a laser directly
(without an additional transistor). With one transistor, you can also switch several lasers
together.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 252 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

11.5 SD card module


Sometimes you need more
data than the ATmega328,
with its 32 KB program mem-
ory and mere 2 KB of RAM
can handle. In these cases,
an SD card module is recom-
mended, such as the one in
the picture on the right. Fig. 11.5a: MicroSD card and adapter board

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.

Connection to the Arduino


The SD card module is controlled via an SPI interface—a
serial interface that the ATmega328 also has,
fortunately. For the three lines, MOSI, MISO, and SCK,
we have to use digital Pins 11, 12, and 13 on the
Arduino. Another pin, CS, gives indicates whether a card
is inserted. Pin 10 is usually used for this purpose.

If we use a Pro Mini board with pin headers, we can wire


Fig. 11.5b:
the connections into the SD card module directly using 6
Connection diagram
jumper wires. Besides the 4 signal lines, VCC and GND
are also connected. For a permanent setup, it is of course better to solder the wires. We
can also put a 6-pin female header on the SD card module and solder the rest. This
way, the module is still pluggable, but has a better connection than with individually
plugged wires.

256

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 253 2022-07-27 20:20


Chapter 11 • Other components

Syntax: SD.h file functions

#include <SD.h> // Include library for SD card functions

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.

Sketch: Reading and writing files 11_5.ino


#include <SPI.h> // include library for SPI interface
#include <SD.h> // library for the SD card functions

#define chipSelect 10 // pin to CS pin on SD card module

File testfile; // declares the file used later

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

if (SD.begin(chipSelect)) // if card is detected


{
Serial.println("Card found!");
}
else
{
Serial.println("Error: No card found!");
while (true); // do not continue
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 254 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

testfile = SD.open("test.txt", FILE_WRITE); // open file for writing


if (testfile) // if file could be opened
{
Serial.println("File opened.");
testfile.println("This is the first line."); // write to file
testfile.write(83); // write single bytes to the file
testfile.write(101); testfile.write(99); testfile.write(111);
testfile.write(110); testfile.write(100); testfile.write(32);
testfile.write(76); testfile.write(105); testfile.write(110);
testfile.write(101); testfile.write(33);
testfile.close(); // cloes file
Serial.println("Data written.");
}
else // if file could not be opened
{
Serial.println("Error: File could not be opened!");
while (true); // do not continue
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 255 2022-07-27 20:20


Chapter 11 • Other components

testfile = SD.open("test.txt"); // Open file for reading


if (testfile) // if file could be opened
{
Serial.println("Read file:");
while (testfile.available()) // as long as file data is available
{
Serial.write(testfile.read()); // read byte and send serially
}
testfile.close(); // close file
}
else // if file could not be opened
{
Serial.println("Error: Unable to read the file.");
}
}

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!”

void loop() // There's no more left to do here.


{
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 256 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Chapter 12 • Rechargeable batteries and accessories


Until the end of the last century, small rechargeable batteries were nearly invariably
nickel-based (Ni-Cd, later Ni-MH), but today portable devices almost always have
lithium-ion batteries. Again, there are numerous versions of these, e.g. the lithium
polymer type. Fortunately, there are no major differences in dealing with them. Almost
all lithium batteries have a nominal voltage of 3.6 or 3.7 volts and can be charged up to
4.2 volts. Some new types even tolerate a bit more, up to 4.35 or 4.4 volts.

Fig. 12a: Various lithium-polymer batteries

Lithium-ion batteries come in all conceivable sizes and designs. Permanently-installed


rechargeables are often form-factored as gray, box-shaped, lithium polymer batteries,
which come in different lengths, widths and thicknesses, with capacities from about
40 mAh to 40 Ah. Usually, the dimensions are printed in the form of a 6-digit number,
with the first two digits indicating the thickness in tenths of a millimeter, followed by
two digits indicating the width in millimeters, and the last two digits indicating the
length, also in millimeters. For large batteries of 10 cm or more in length, this number
has 7 digits.
Another very common type is 18650, which are round
cells, 18 mm in diameter and 65 mm in length, with
capacities from 1000 to 3500 mAh. (Much higher figures
are fantasy values that have nothing to do with reality).
Some cells have a small protrusion at the positive pole,
and can thus be inserted into an appropriate battery
compartment or charger. From China, for example, you
can get all kinds of flashlights that run on 18650 cells. In
the EU, however, devices with replaceable 18650 cells
are not as widespread, but you can find these batteries
everywhere. Almost every laptop battery contains 6 such
cells, and, no, this is not a joke: Many electric cars run
on 18650 cells. But there are a few more – e.g. 2000 or
3000 cells in an e-car battery.

← Fig. 12b: 18650 rechargeables

260

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 257 2022-07-27 20:20


Chapter 12 • Rechargeable batteries and accessories

Tip: Soldering round cells


Round cells (such as 18650 batteries) are often offered with solder lugs that were
spot-welded, because you should not actually solder such batteries directly – the
problem is the high temperature that’s needed for soldering, which the battery does
not tolerate well. In addition, the battery casing conducts heat very well, which
makes soldering even more difficult.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 258 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

12.1 Functionality and handling


While other batteries never really tell us when they’re done charging, it’s actually quite
simple with lithium-ions: They’re full when they’ve reached the final charging voltage of
4.2 volts (with some types, up to 4.45 volts). However, this is also what makes them so
dangerous, because if we were to continue charging them, their voltage would also
continue to rise, and they don’t like that at all. The allowable upward tolerance is
usually just 50 mV (so a total of 4.25 volts). I once accidentally charged a lithium-ion
battery to over 4.6 volts because the charger was defective. Fortunately, nothing
happened. However, the battery could also have extracted vengeance with a nasty
explosion.

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.

12.2 Protection circuit


Some lithium-ion batteries have built-in protection circuits that prevent overcharging as
well as discharging too deeply. In both cases, the protection circuits simply disconnect
the cells. Often, the protection also includes over-current / short-circuit protection.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 259 2022-07-27 20:20


Chapter 12 • Rechargeable batteries and accessories

very cheaply in different


sizes and shapes, directly
from China, for 2 or 3 euro
for a pack of ten.

Protection circuits for a


Fig. 12.2: Two protection circuits, top and underside
single battery cell or for
several cells connected in parallel usually have 4 connections. The battery is connected
to B+ and B-. P+ and P- become the new connections, where the battery is charged
and placed under load. Such protection circuits are often marked 1S. Often, B+ is also
identical to P+.

12.3 Connecting rechargeables in series


You can also connect several batteries in series, and thus, for example, double or triple
the total voltage. With lithium-ion batteries, however, this is not so easy, because the
cells then have to be monitored individually (in contrast with a parallel connection). For
this, there are special protection circuits for 2, 3, 4, or even more cells in series. The
corresponding designations are 2S, 3S, etc. The number in front of the S indicates how
many batteries are connected in series.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 260 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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 solution is a so-called balancer, which slightly


loads the first (almost) full cells during charging, so
that they’re charged more slowly, or even dis-
charged a bit (this depends on the charge current).
The cells’ different charge states are thus equalized
each time the charging process is carried out.

Most protection circuits, from the 2S upward,


already have a balancer, but you shouldn’t rely on it.
You can identify it there as part of the circuit. It Fig. 12.4: Balancer for a 4S
looks like that pictured here – with somewhat larger resistors (depending on the
number of cells to load these a little) and a few smaller components next to each large
resistor, each laid out identically.

12.5 USB charging regulators


Most of the time, we only
need a small battery volt-
age, so a single battery
cell is enough.

These two charge con-


trollers with Micro USB
sockets are very popular.
The slightly smaller board
Fig. 12.5 USB charging regulator
on the left contains only
without and with protection circuit
a charge controller that
regulates the charge current and limits the battery voltage to 4.2 volts. It can’t protect
against excessive discharging, because the load is directly connected to the battery con-
nections (BAT).

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 261 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Chapter 13 • Clever hacks and tricks


With some components, you can do amazing things, or solve problems in unusual ways
if you’re a little creative and reach into the bag of tricks. Then, the Pro Mini can even
measure its own operating voltage, without a single additional component. Or, we can
put it to sleep, so that it consumes almost no power, and later we simply wake it up
again. We can also do amazing things with the infrared sensor—an item that costs only
a few cents—by turning the principle of the universal remote control on its head.

13.1 Measuring battery level without components


For battery-powered devices, it can be very useful to measure the battery level
repeatedly. With rechargeable batteries, this is especially important, so that you can
switch off the Arduino automatically when the battery is nearly empty, in order to
protect it from too deep a discharge. Also, measurement of the board’s own operating
voltage (VCC) is often useful.

Usually, the operating voltage is applied to a voltage divider,


which divides it by 5, for example. This way, you get a voltage
below one volt, which is applied to an analog input. The
internal 1.1-volt reference voltage can now be selected as the
measurement range, because this remains very stable,
independent of the operating voltage. We’re already familiar
with this kind of measurement from section 6.2.2. The only
difference now is that we do not measure just any voltage,
but the battery voltage (VCC).
Fig. 13.1:
The disadvantage is that we need two extra resistors for the Conventional
voltage divider. In order not to add additional load to the measurement method
battery, the voltage divider must be very high impedance,
which in turn requires a capacitor for smoothing. In addition, the voltage divider should
also be very accurate, so that the measured value is not unnecessarily falsified.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 262 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

A weird measurement method


When measuring, one usually has a known measuring range within which one measures
an unknown value. We now do it the other way around! We determine an unknown
measuring range by measuring a known value. The ATmega328 (and 168) can not only
measure the voltages at its analog inputs, but also the internal 1.1-volt reference
voltage. This doesn’t seem to make any sense, because we get 100% (the maximum
value of 1023), but this is only true if we choose the reference voltage as measuring
range. If we define VCC (i.e. the battery voltage) as the measuring range, then it
suddenly makes a lot of sense: We can actually determine the exact battery voltage
without any external circuitry – just with an internal measurement.

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.

Sketch: Measuring battery voltage without components 13_1.ino

#define reference 1.1 // internal reference voltage (calibrate if necessary!)

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 263 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

float batVoltage() // determines the battery voltage and returns it


{
byte ADCSRB_before = ADCSRB; // save actual register values
byte ADCSRA_before = ADCSRA;
byte ADMUX_before = ADMUX;
ADCSRB = 0; // reset ADCSRB register
ADMUX = 64 + 14; // measuring range VCC, right adjusted, input Ref 1.1V
ADCSRA = 6; // prescaler to 64
ADCSRA |= (1 << ADATE); // enable Auto Trigger
ADCSRA |= (1 << ADEN); // activate ADC
ADCSRA |= (1 << ADSC); // start measurements
unsigned int adc_16 = 0; // reset summed value
for (byte n=0; n<64; n++) // 64 measurements with 10 bit result in 16 bit value
{
while (!(ADCSRA & 16)); // wait as long as actual measurement is still running
adc_16 += ADC; // add measured value
ADCSRA |= 16; // set ADIF bit to clear it
}
ADMUX = ADMUX_before; // reset registers
ADCSRA = ADCSRA_before;
ADCSRB = ADCSRB_before;
return reference * 65536 / adc_16; // calculate voltage and return it
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 264 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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:

reference = reference_old * actual_voltage / monitor_voltage

reference is the newly determined reference voltage, reference_old is the


previously-entered value (1.1 volts). actual_voltage is provided by the multimeter, and
monitor_voltage is read from Serial Monitor. When we’ve determined the exact
reference voltage, we only have to enter it in the sketch and reload the program. For
control, we measure again, and the voltages at the multimeter and at the monitor
should now be almost identical.

13.2 Arduino in deep sleep


Of course, measuring the battery voltage only really makes sense if we’re also able to
switch off the device automatically when the battery is low in order to protect it, and
that’s exactly what the ATmega328 (or 186) can actually do! It can shut itself down (in
parts or completely) and then consume almost no power when shut down. To revive it
later, we simply have to reapply the voltage or press the Reset button.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 265 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Sketch: Sleep mode (bare template) 13_2_a.ino

#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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 266 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Sleep mode (with wake-up pin) 13_2_b.ino

#include <avr/sleep.h>

#define wake_pin 2 // only 2 and 3 usable

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 267 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.

← Fig. 13.2: Wake-up switch

13.3 Low-battery switch-off


Now we’re ready! We can combine the battery measurement sketch with the standby
one, so that our Arduino-based device switches off automatically when the battery is
low. We can select the shutdown voltage freely, e.g. 3.15 volts.

271

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 268 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Low-battery switch-off 13_3.ino

#include <avr/sleep.h>

#define reference 1.1 // internal reference voltage (calibrate if necessary!)

float shutdown_voltage = 3.15; // battery cut-off at 3.15 volts

void setup()
{
Serial.begin(38400); // prepare serial transmission
}

This program is a combination of internal battery-voltage measurement (without


external components) and shutdown (without a wake-up input). This part is new:

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 269 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

float batVoltage() // determines the battery voltage and returns it


{
byte ADCSRB_before = ADCSRB; // save actual register values
byte ADCSRA_before = ADCSRA;
byte ADMUX_before = ADMUX;
ADCSRB = 0; // reset ADCSRB register
ADMUX = 64 + 14; // measuring range VCC, right adjusted, input Ref 1.1V
ADCSRA = 6; // prescaler to 64
ADCSRA |= (1 << ADATE); // enable Auto Trigger
ADCSRA |= (1 << ADEN); // activate ADC
ADCSRA |= (1 << ADSC); // start measurements
unsigned int adc_16 = 0; // reset summed value
for (byte n=0; n<64; n++) // 64 measurements with 10 bit result in 16 bit value
{
while (!(ADCSRA & 16)); // wait as long as actual measurement is still running
adc_16 += ADC; // add measured value
ADCSRA |= 16; // set ADIF bit to clear it
}
ADMUX = ADMUX_before; // reset registers
ADCSRA = ADCSRA_before;
ADCSRB = ADCSRB_before;
return reference * 65536 / adc_16; // calculate voltage and return it
}

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.

Integrating low-battery switch-off into projects


Besides the actual verification and shutdown, the previous sketch uses serial output to
output the battery voltage. Sketch 13.3b has a cut-down version that contains only the
shutdown, as well as many comments explaining how to integrate the shutdown into
other projects.

273

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 270 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

13.4 Pro Mini battery operation


For battery operation with lithium-ion battery or three 1.5 V batteries, the clock
frequency must be only 8 MHz. LilyPads are optimal for the purpose. But, be careful
with offers from China, as there are cheap LilyPads offered as 16 MHz versions by
default, which is less optimal for battery operation. In addition, this LilyPad version is
not official, so you have to select the Pro Mini at 16 MHz for uploading, otherwise the
timers and delays will run twice as fast.

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.

Reducing current consumption


It can thus be useful to remove these components to minimize power consumption.
With a very hot soldering iron and some fresh solder, this can be done quite easily.
These SMD components are too small for us to solder, but if we go at it with a hot
soldering iron and a fresh drop of solder, they come right off and you can strip them off
the board that way, using the soldering tip. We just have to be careful not to create a
short circuit on the board. Any solder bridges can be easily removed by adding a fresh
drop of solder (even if this adds to the short circuit at first). If we hold the board
vertically and wipe the fresh solder with the soldering tip downwards over the edge of
the board, it flows to the soldering tip and the short circuits are removed. But, for this
we should set the soldering station’s temperature lower again – normal soldering
temperature is quite sufficient here.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 271 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.)

13.5 Project: Electronic die


The electronic die is a wonderful example of how standby mode and low-battery
shutdown can be applied practically. If you press the Reset button or another button
that you connect, the 7 LEDs display different patterns, corresponding with the numbers
on the faces of a die. The rapidly-changing numbers then slow down, and, after
2 seconds, the display stops. The rolled number remains displayed until you roll the
dice again.

If you don’t press the button for a We need:


full minute, the die will automatically
turn off and remain in Standby mode • 1 Board (8 MHz ATmega328 or 168)
until the button is pressed again. If • 7 resistors, 220 Ω / ¼ W (see text)
the battery voltage drops below the • 1 resistor, 47 kΩ / ¼ W
minimum voltage, the die refuses to • 1 push-button (not totally necessary)
show anything and immediately goes • 7 LEDs (same color, see text)
into Standby mode. Only when we • 1 small lithium-ion rechargeable
recharge the battery a bit, can we • 1 charge controller with Micro USB socket
roll the die again.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 272 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

← Fig. 13.5b: Assembly


  example with laser cut
wooden housing

276

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 273 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Now, for the first time, a sketch that also uses the ATmega chip’s internal EEPROM. For
this, first a brief explanation:

Syntax: EEPROM functions


With the EEPROM functions, you can store data in the Arduino and read it later as
with a memory card. This means that, even if you remove the operating voltage and
turn the board on later, this data is still there.

#include <EEPROM.h> // include EEPROM functions

Here, we include EEPROM.h right at the beginning, in order to use the functions

EEPROM.update(392, 57); // write a byte

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.

x = EEPROM.read(392); // read an EEPROM memory location (byte)

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 274 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Electronic die 13_5.ino

#include <EEPROM.h>
#include <avr/sleep.h>

#define reference 1.1 // internal reference voltage


#define wake_pin 2 // push button pin (must be 2 or 3)
#define led_1 3 // top left
#define led_2 4 // center left
#define led_3 5 // bottom left
#define led_4 6 // top right
#define led_5 7 // center right
#define led_6 8 // bottom right
#define led_7 9 // center center

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.

float off_voltage = 3.15; // shutdown voltage


bool active = HIGH; // HIGH when LEDs against ground, LOW when against plus
byte changes = 10; // how often the display is changed when rolling the dice
int first_time = 80; // how fast (in ms) the display changes first
int last_time = 500; // how fast (in ms) the display changes last
long display_time = 60000; // time (in ms) until display goes out

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.

byte PRR_reg; // buffer for PRR register


byte w_value; // diced value (1 to 6)
byte before = 0; // previous value (in quick change)
byte before_before = 0; // second-last value (in quick change)
long display_counter; // remaining display time
int real_time; // actual display time (between first_time and last_time)
byte n; // count variable
long random_pos; // position in the random generator
long new_random_pos = 0; // new position in the random generator
boolean pressed = false; // true when button is pressed

278

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 275 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Then, the remaining variables that are needed. There are no further settings.

void setup()
{
pinMode(wake_pin, INPUT_PULLUP);

pinMode(led_1, OUTPUT); // dice LEDs


digitalWrite(led_1, !active); // switch off

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.

random_pos = long(EEPROM.read(121)) << 24; // HSB


random_pos += long(EEPROM.read(122)) << 16;
random_pos += long(EEPROM.read(123)) << 8;
random_pos += long(EEPROM.read(124)) << 0; // LSB
randomSeed(random_pos); // set random function to position
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 276 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

before_before = before; // update second last value


before = w_value; // update last value

if (w_value == 1) // switch LEDs according to the dice number


{
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);
}

280

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 277 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 278 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

// Calculate actual time between first_time and last_time:


real_time = round(1 / ((((float(1) / first_time) * (changes - n)) + ((float(1) /
last_time) * n)) / float(changes)));
delay(real_time); // delay till the next change
}

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.

if (!new_random_pos) // if random number position is not stored yet


{
new_random_pos = random_pos + micros(); // add system time (in µs)
EEPROM.update(121, (new_random_pos >> 24) & 255); // HSB
EEPROM.update(122, (new_random_pos >> 16) & 255);
EEPROM.update(123, (new_random_pos >> 8) & 255);
EEPROM.update(124, (new_random_pos >> 0) & 255); // LSB
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 279 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

display_counter = display_time; // remaining display time till LEDs go out


while (display_counter && !pressed) // while time remains and button not pressed
{
display_counter--;
delay(1); // 1ms
pressed = !digitalRead(wake_pin); // pushbutton against GND (true when LOW)
}
}

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”.

else // if battery is empty


{
// Here you could include an action that only runs when the battery is empty.
// E.g. you could let the middle LED flash for 100 ms:
// digitalWrite(led_7, active);
// delay(100);
}

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.

digitalWrite(led_1, !active); // all LEDs off


digitalWrite(led_2, !active);
digitalWrite(led_3, !active);
digitalWrite(led_4, !active);
digitalWrite(led_5, !active);
digitalWrite(led_6, !active);
digitalWrite(led_7, !active);

if (!pressed) // if button was not pressed


{
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // preset to deep sleep
// wake up on button press (on falling edge at wake_pin)
attachInterrupt(digitalPinToInterrupt(wake_pin), wakeUp, LOW);
PRR_reg = PRR;

283

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 280 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

sleep_enable(); // activate sleep mode


PRR |= 64 + 32 + 8 + 4 + 2; // disable timer, SPI and USART
sleep_mode(); // trigger sleep mode
}
}

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.

void wakeUp() // interrupt function (when button is pressed in sleep mode)


{
detachInterrupt(digitalPinToInterrupt(wake_pin)); // reset interrupt
PRR = PRR_reg; // re-enable components
}

float batVoltage() // determines the battery voltage and returns it


{
byte ADCSRB_before = ADCSRB; // save actual register values
byte ADCSRA_before = ADCSRA;
byte ADMUX_before = ADMUX;
ADCSRB = 0; // reset ADCSRB register
ADMUX = 64 + 14; // measuring range VCC, right adjusted, input Ref 1.1V
ADCSRA = 6; // prescaler to 64
ADCSRA |= (1 << ADATE); // enable Auto Trigger
ADCSRA |= (1 << ADEN); // activate ADC
ADCSRA |= (1 << ADSC); // start measurements
unsigned int adc_16 = 0; // reset summed value
for (byte n=0; n<64; n++) // 64 measurements with 10 bit result in 16 bit value
{
while (!(ADCSRA & 16)); // wait as long as actual measurement is still running
adc_16 += ADC; // add measured value
ADCSRA |= 16; // set ADIF bit to clear it
}
ADMUX = ADMUX_before; // reset registers
ADCSRA = ADCSRA_before;
ADCSRB = ADCSRB_before;
return reference * 65536 / adc_16; // calculate voltage and return it
}

Left now is just the wakeUp() function, which we’ve met in section 13.2, and
batVoltage(), from section 13.1.

284

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 281 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Tip: LED effect board as a die


If you built the LED effect board from section 7.1.4, you can also load the die sketch
onto it and use the effect board as a die. We only have to make two small changes to
the program sketch. In the variable block above, the “active” variable has to be set to
LOW instead of HIGH, because, on the effect board, the LEDs are connected to the
positive pole and thus active-LOW. Also, we might want to add the following line in
setup():

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.

Dice for cheaters


Now, of course, it would also be possible to build hidden tricks into such an electronic
game cube, for example to roll a 6 when needed. You could also influence the behavior
of the die depending on how often or how long you press the button. You could also put
a very small hole on the housing with an LDR (light-dependent resistor) behind it. This
way, you could give a signal to the cube by holding it while rolling the dice so that the
hole is covered. There are no limits to the imagination.

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.

13.6 Analog measurement without waiting


An analog measurement always needs several microseconds, which can be very
undesirable if the sketch has to do other things at the same time. If fast timing has to
be adhered to as well, the Arduino quickly reaches its limits. Should we need analog
measurements in rapid succession, e.g. if an analog audio signal is to be read in and
processed, the Arduino is already fully utilized doing the measurements alone.

285

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 282 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Wouldn’t it be great if the analog measurements could run automatically in the


background, and we only had to pick up the measured values without waiting for the
measurement each time? This is exactly what the ATmega328 and 168 can do, and this
is exactly what we’ll do next. For this, we only have to replace the familiar analogRead()
with some of our own functions.

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.

Sketch: Continuous analog measurement 13_6.ino

void setup()
{
Serial.begin(38400); // prepare serial transmission

analogSelect(A0, DEFAULT); // select analog pin and reference


// pin: A0 to A7, reference: DEFAULT, INTERNAL (or EXTERNAL)
// Instead of the pin, you can also specify 6 to measure temperature,
// 12 to measure the internal reference voltage or 13 to measure GND (0).

freeRunning(1); // Free Running mode with fast speed


// At 16 MHz 0 is the default speed, but at 8 MHz the default is 1.
// Increasing the speed by 1 is twice as fast in each case.
// There are approx. 9615 measurements per second at standard speed.
// The speed should not be more than doubled. Else it becomes inaccurate.
}

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).

The freeRunning() function starts continuous measurement (Free-Running mode).


Here, we also specify the speed at which the analog-to-digital converter should
operate. For 16 MHz, 0 corresponds to the normal default speed. For an 8 MHz
Arduino, 1 is the default. If we increase this default value by 1, the converter works
twice as fast, which hardly affects the quality of the measurement. If we increase it
again by 1, we get 4 times the speed, which can introduce significant inaccuracy.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 283 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.

void analogSelect(byte analog_channel, byte analog_reference) // pin & reference


{
analog_reference &= B00000011; // reduce to 2 bits (0 to 3)
if (analog_channel >= 14 && analog_channel < 22) // if valid analog pin
{
pinMode(analog_channel, INPUT); // measuring pin as input
}
analog_channel -= 14; // convert to register value (A0 -> 0, A1 -> 1, ...)
analog_channel &= B00001111; // only 4 bit (0 to 15)
ADMUX = analog_channel | (analog_reference << 6); // compose register byte
}

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.

The following explanation is about communicating with the microcontroller’s special


registers. To understand the details, you should have a look at the microcontroller’s
datasheet. But, we don't have to understand it all – I explain it here only for the sake
of completeness for those who want to deal with it in depth. To use the functions, it is
sufficient to know what they do, not how they do it.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 284 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

void freeRunning(byte adc_speed) // start Free Running Mode (default value is 0)


{
adc_speed &= B00000111; // only 3 bit (0 to 7)
ADCSRB = 0; // reset ADCSRB register
ADCSRA = 7 - adc_speed; // set prescaler (0 -> 128, 1 -> 64, 2 -> 32, 3 -> 16)
ADCSRA |= (1 << ADATE); // enable Auto Trigger
ADCSRA |= (1 << ADEN); // activate ADC
ADCSRA |= (1 << ADSC); // start measurements
while (!(ADCSRA & (1 << ADIF))); // wait for first measurement
ADCSRA |= (1 << ADIF); // set ADIF bit to clear it
}

With the freeRunning() function, we begin continuous measurement (Free-Running


mode). A value is passed that determines the prescaler and thus the speed of the
analog-to-digital converter. This value is truncated to 3 bits, because the number must
not be larger than 7, and there are only 3 bits for it in the register. In register
ADCSRB, all bits are cleared (to 0). In register ADCSRA, the speed value is written
inverted (hence “7 - adc_speed”) into the lower 3 bits (0 to 2). The remaining bits of
ADCSRA are cleared first. Then, three bits are set, one after the other, to start Free-
Running mode. Now, the function could be terminated, but instead, the “while” waits
for the first measurement result and then resets the ADIF bit to indicate that the value
is read, because the first measurement result may still be inaccurate.

bool valueAvailable() // returns true if unread measurement value is present


{
return ADCSRA & (1 << ADIF); // true, if new measured value is ready
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 285 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.

Value 8 MHz Arduino 16 MHz Arduino


0 approx. 208 µs 4807 / s (slow) approx. 104 µs 9615 / s (regular)
1 approx. 104 µs 9615 / s (regular) approx. 52 µs 19230 / s (fast)
2 approx. 52 µs 19230 / s (fast) approx. 26 µs 38460 / s
3 approx. 26 µs 38460 / s approx. 13 µs 76920 / s

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 286 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 287 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.

13.7 Project: Universal remote control receiver


We probably all know universal remote controls that can be used on different devices
from different manufacturers by selecting the appropriate codes. Some remote controls
can also learn the codes and then replace the original remote control.

Turning the principle on its head


Let’s turn the principle of the universal remote control around! We can operate this
universal remote receiver with any button from any remote control by simply teaching it
briefly, using the corresponding infrared signal. This really works with almost all remote
controls.

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:

• 1 ATmega328 board at 16 MHz (for fewer channels, the ATmega168 suffices)


• 1 Infrared receiver for 38 kHz signals, e.g. VS1838B, TL1838, or IR1261
• 1 suitable power supply for a Pro Mini board, min. 5 V / max. 12 V
• Possibly transistors or solid-state relays, depending on what you want to switch

291

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 288 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Depending on what we want to


switch with it, maybe one tran-
sistor and one resistor or one
solid-state relay is needed per
channel used. And that’s it!

Fig. 13.7a: Simple construction of a


universal remote receiver

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 289 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Fig. 13.7d: Demodulation of the infrared data

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 290 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: 10-channel universal remote receiver 13_7.ino


#define fb_pin 2 // input to which the VS1838B infrared receiver is attached
#include <EEPROM.h> // integrate EEPROM functions

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.

int eeprom_size = 1024; // 1024 for ATmega328, 512 for ATmega168


int eeprom_first_pos = 10; // first EEPROM memory location for code (at least 10)
byte max_code_len = 70; // maximum length of a key code
byte min_code_len = 14; // minimum length of a key code
byte max_channels = 10; // maximum usable control channels (1 - 10)
int learn_time = 10000; // learning time per channel in milliseconds (10 seconds)
byte pulse_tolerance = 1; // permitted time deviation of the pulses
byte error_tolerance = 0; // permitted number of errors

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.

In max_channels, we can specify how many channels we need at maximum.


learn_time specifies how long the Arduino remains in Learn mode after power-on.
(Only if a signal is received in that time, will the learn time be run for the other
channels, as well.)

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.

byte n; // count byte


bool fb_state = 1; // actual pulse state
byte fb_time = 0; // duration of the actual pulse
byte fb_data[120]; // data array for code
byte fb_data_size = 0; // actual length of the received code

294

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 291 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

unsigned int eeprom_pos; // actual position in EEPROM


byte code_len; // code length (for comparison)
byte wrong_bits; // counts deviations in the code
byte next_time; // time for next loop pass
byte loop_time = 192; // loop time (in microseconds)
byte learn_channel = 1; // channel that is currently being learned
byte channel = 0; // channel that is switched
char difference; // time deviation of the pulse duration

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 292 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

while (byte(next_time - micros())); // wait until time is reached


next_time += loop_time; // set next time

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.

fb_time++; // passes since last level change of the IR signal


if (fb_time >= 84) // if no level change for a longer time (approx. 16ms)
{
if (fb_data_size > min_code_len) // if min required amount of data available
{

296

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 293 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.

if (learn_channel) // if program is (still) in learning mode


{
if (eeprom_pos + fb_data_size < eeprom_size - 3) // if memory is sufficient
{
if(channel < learn_channel) // if new channel
{
EEPROM.write(eeprom_pos, 254); // write mark for next channel
eeprom_pos++; // adjust EEPROM position
channel = learn_channel; // actualize channel number
}
EEPROM.write(eeprom_pos, fb_data_size); // save code length
for (n=1; n<fb_data_size; n++) // save code (starting from 1 instead of 0)
{
EEPROM.write(eeprom_pos + n, fb_data[n]); // save byte
}
eeprom_pos += fb_data_size; // adjust EEPROM position
EEPROM.write(eeprom_pos, 255); // mark end (when no more data follow)
Serial.print("Remote signal (channel "); // display message
Serial.print(learn_channel); // channel number
Serial.print(") saved:");
for (n=1; n<fb_data_size; n++) // single bytes of the code
{
Serial.print(" ");
Serial.print(fb_data[n],HEX); // display byte hexadecimal
}
Serial.println(""); // new line
blink(1,300); // onBoard LED flashing 1 time 300 ms
}
else // when EEPROM is full
{
Serial.println("EEPROM full!"); // display message
blink(20,75); // onBoard LED flashing 20 times 75ms
}
}

297

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 294 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

else // when program is in control mode


{
eeprom_pos = eeprom_first_pos; // read position for EEPROM at beginning
channel = 0; // reset channel
while (EEPROM.read(eeprom_pos) < 255) // as long as further code available
{
if (EEPROM.read(eeprom_pos) == 254) // marker for next channel
{
channel++; // next channel
eeprom_pos++; // adjust EEPROM read position
}
code_len = min(EEPROM.read(eeprom_pos), fb_data_size); // the shorter code
wrong_bits = 0; // reset error counter
for (n=1; n<code_len; n++) // for all bytes of the code
{
difference = EEPROM.read(eeprom_pos + n) - fb_data[n]; // deviation
// If deviation is greater than +/-tolerance:

298

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 295 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

if (difference < -pulse_tolerance || difference > pulse_tolerance)


{
wrong_bits++; // count deviation
}
}
eeprom_pos += EEPROM.read(eeprom_pos); // actualize EEPROM position
if (wrong_bits <= error_tolerance) // if deviations within tolerance range
{
// Found a known code!!!
eeprom_pos = eeprom_size - 1; // last EEPROM position (end search)
if (EEPROM.read(eeprom_first_pos - channel) == 99) // if channel was on
{
digitalWrite(channel + 2, LOW); // switch channel off
EEPROM.write(eeprom_first_pos - channel, 255); // save channel state
}
else // if channel was switched off till now
{
digitalWrite(channel + 2, HIGH); // switch channel on
EEPROM.write(eeprom_first_pos - channel, 99); // save channel state
}
Serial.print("Signal (channel "); // display message
Serial.print(channel); // channel number
Serial.println(") detected!");
blink(1,300); // onBoard LED flashing 1 time 300 ms

// Here we can insert other actions that will be executed,


// when a valid remote control signal is received. Thereby
// we can also remove the original actions (from the "if" on).
// Also extensive program codes (that last) are possible here.
}
}
if (eeprom_pos < eeprom_size - 1) // if no matching signal was found
{
Serial.println("Unknown signal received!"); // display message
blink(1,100); // onBoard LED flashing 1 time 100 ms
}
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 296 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 297 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

fb_data_size = 0; // reset data position


fb_time = 0; // reset level duration
}
else if (digitalRead(fb_pin) != fb_state) // at level change
{
fb_state = !fb_state; // toggle level state
if (fb_data_size < max_code_len) // if maximum code length not yet reached
{
fb_data[fb_data_size] = fb_time; // add data byte
fb_data_size++; // actualize code size
}
fb_time = 0; // reset level duration
}
// Here you can insert your own (small) program code.
// This code is executed 5208.333 times per second.
// Therefore the execution (together with the previous
// source code) may only take 192 µs. If a valid remote
// signal has just been detected, however, it is delayed.
}

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.

void blink(int times, int duration)


{
for (int count=0; count<times; count++)
{
if (count) delay(duration); // delay from second pass on
digitalWrite(13, HIGH); // onBoard LED on
delay(duration);
digitalWrite(13, LOW); // onBoard-LED off
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 298 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

Teaching the receiver


Before we teach the receiver with the key codes, all of our remote controls should be
ready (if using more than one) and we should already know exactly how each channel
will be stored. It also makes sense to connect the serial cable and open Serial Monitor
so that we can see what the receiver is doing.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 299 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 300 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Tip: Exact clocking of the loop with one byte


Sometimes it is necessary (as in the case of the remote control receiver) to clock the
loop precisely with a given time unit.

byte next_time = 0; // Time for the next loop pass


byte loop_time = 192; // Loop duration (in microseconds)
void loop()
{
while (byte(next_time - micros())); // Wait until 192 µs has passed in total
next_time += loop_time; // set the next duration
// Here’s the loop’s actual content, which is carried out every 192 µs
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 301 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Tip: Exact clocking of the loop using an integer


If we need a longer clock time that cannot be specified in a single byte, i.e. durations
that are longer than 255 µs, then we can do the same clocking using an integer value
(which consists of two bytes):

int next_time = 0; // Time until the next loop procession


int loop_time = 1920; // Loop duration (in microseconds)
void loop()
{
while (int(next_time - micros())); // wait until a total of 1920 µs has passed
next_time += loop_time; // set the next time
// Here’s the actual content of the loop, which is executed every 1920 µs
}

As an example here, we have 10 times the previous duration, or 1920 µs instead of


192 µs, and now everything is calculated and compared using an int. Again, overflows
don’t matter, and it also doesn’t matter if we get negative numbers if we use either
int or unsigned_int. The “while” loop is always exited only when the difference
between next_time and the system time, interpreted as int (or unsigned_int) results
in 0, and this is the case when next_time has expired.

Again, the clock duration, loop_time, must always be divisible by 4. Otherwise, we


get stuck in an endless loop, because system time micros() runs in steps of 4. Again,
with an 8 MHz clock frequency (instead of 16 MHz) they are steps of 8 and loop_time
must be divisible by 8.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 302 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Tip: Clocking of the loop with lateness options


If a loop takes a little longer and cannot stick to the maximum duration, we have the
problem with clocking using integer in that the next cycle takes place more than
65,536 µs later, i.e. there is a delay of over 65 ms. We can work around this problem
by exiting the “while” loop not only when the difference is 0, but also when the result
is negative. That then looks like this:

int next_time = 0; // Time until the next loop procession


int loop_time = 1920; // Loop duration (in microseconds)
void loop()
{
while ((next_time - int(micros())) > 0); // wait at least 1920 µs
next_time += loop_time; // set the next time
// Here’s the actual content of the loop, which is executed every 1920 µs
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 303 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

Tip: Clocking of the loop using only system time


There is also an even simpler way to clock the loop precisely in constant time units,
for which we need no variable at all besides the system time. But, for this we have to
limit ourselves to durations in microseconds that are a power of two, for example
128, 256, 512, or 1024 µs.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 304 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

13.8 Project: Extreme altimeter


In section 10.2.5 we already got to know an altimeter with the BMP180 pressure sensor.
Now, we increase the accuracy again by measuring continuously and obtaining the
displayed values from hundreds of individual measurements. In addition, we can display
altitude changes in centimeters. In this way, we can even detect altitude differences of
20 or 30 centimeters within a room.

We need:

• 1 board (e.g. Pro Mini) with ATmega328 or ATmega168 at 8 or 16 MHz


• 1 barometric pressure sensor board with a BMP180 sensor
• 1 serial-to-USB adapter (for power supply and Serial Monitor)
• 4 short wires, e.g. jumpers

Sketch: Extreme altimeter 13_8.ino

#include <Wire.h> // I2C library


#define address 0x77 // I2C 7-bit address of the BMP180

char oversampling = 3; // 0-> 1x , 1 -> 2x , 2 -> 4x , 3 -> 8x


char over_oversampling = 6; // 0-> 1x ... 8 -> 256x ... 14 -> 16384x
byte comp_count = 1; // number of previous values to compare with, 1 to 10

int duration[4] = {4500, 7500, 13500, 25500}; // times (µs) depending on


oversampling
long alt_before[10]; // the previous altitude values
int delta_alt; // altitude change
byte counter; // count variable
int ac1, ac2, ac3, b1, b2, mb, mc, md, temperature; // several variables
unsigned int ac4, ac5, ac6, t, n, next_time;
long x1, x2, x3, b3, b5, b6, p, pressure, altitude, altitudes = 0;
unsigned long b4, b7, p_added;

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 305 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

void setup()
{
Wire.begin(); // prepare I2C transmission

Wire.beginTransmission(address); // select device (BMP180)


Wire.write(0xAA); // start position of calibration data in the BMP180 chip
Wire.endTransmission();

Wire.requestFrom(address,22); // read calibration data (22 bytes resp. 11 int values)


ac1 = read_int(); // reads the next two bytes
ac2 = read_int();
ac3 = read_int();
ac4 = read_int();
ac5 = read_int();
ac6 = read_int();
b1 = read_int();
b2 = read_int();
mb = read_int();
mc = read_int();
md = read_int();

Serial.begin(38400); // prepare serial 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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 306 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

altitude = 44330000 * (1 - pow(((float)p_added / ((unsigned long)101325 <<


over_oversampling)), 1 / 5.255)); // altitude in mm
if (!altitudes) // if previous altitudes are not saved yet
{
altitudes = altitude * comp_count; // actual altitude multiplied by number
for (n=0; n<comp_count; n++)
{
alt_before[n] = altitude; // fill array
}
}
// Difference from the average of the last altitude values (rounded):
delta_alt = ((altitude * comp_count) + (comp_count>>1) - altitudes) / comp_count;
counter++; // increase counter
if (counter >= comp_count) // if number is reached
{
counter = 0; // reset (only count from 0 to number minus 1)
}
altitudes += altitude - alt_before[counter]; // add actual value, remove oldest
alt_before[counter] = altitude; // replace the oldest value with the actual one

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 307 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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.

Serial.print("Temp.: "); // display temperature, pressure and altitude


Serial.print(((float)temperature/10), 1);
Serial.print("°C Pressure: ");
Serial.print(((float)p_added/(long(100)<<over_oversampling)), 2);
Serial.print("hPa(mbar) Altitude: ");
Serial.print(((float)altitude/1000), 2);
Serial.print("m Change: ");
Serial.print(delta_alt/10); // from mm to cm
Serial.println("cm");
}

At the end of loop(), the values are output.

[...]

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 308 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Settings and possibilities


If we start the program, we get an output in Serial Monitor something like this:

Fig. 13.8: Outputs to Arduino IDE Serial Monitor

Depending on the preset value of the comp_count


over_ Approximate
variable in the sketch, the last value in the window oversampling measurement
shows the current change compared with the last variable duration
output (at preset 1), or compared to the last (up to
0 0.1 s
10) previous altitude values. How many times to
1 0.1 s
measure before displaying depends on the variables
“oversampling” and over_oversampling. While we 2 0.2 s
best leave “oversampling” at 3, we can adjust 3 0.3 s
over_oversampling from 0 to 15. How long the 4 0.6 s
measurements with the different settings take until 5 1.2 s
the next values are displayed is shown in the table
6 2.4 s
on the right.
7 5s
8 10 s
Measuring small altitude changes
9 20 s
It is best to set the variable over_oversampling to
10 40 s
6, 7, or 8 if the smallest possible changes in
altitude of 20 to 30 cm are to be detected.  The 11 1:20 min
output will then be something like every 2.5, 5, or 12 2:40 min
10 seconds. If the change is to be output only once, 13 5:20 min
and then immediately the current position serves as 14 11 min
15 22 min
312

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 309 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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:

Weather trend barometer


If we set the comp_count variable to 10 and the over_oversampling variable to 14 or
15, we will get the current weather trend every 11 or 22 minutes, or, more precisely,
how the air pressure has changed within the last 100 or 200 minutes, where the last
hour (for a value of 14) or the last 2 hours (for a value of 15) count specifically. On this
basis, we could build an electronic barometer, for example, which shows the trend as
well as the absolute air pressure. It should be noted, however, that pressure and
altitude are contraflowing. In this program, the trend provides us with altitude
information and more altitude means less pressure, i.e. low pressure or bad weather,
while a lower altitude reading stands for high pressure or good weather.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 310 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

314

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 311 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

13.9 Project: Infrasound recorder


Now, let’s do something crazy: Let’s combine air pressure sensor BMP180 with the SD
card module from section 11.5. What could we do with it?

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.

OK, admittedly, the BMP180 is not really suitable as a highly-sensitive microphone.


Although it already detects pressure deviations at low altitude changes, infrasound has
to be quite loud to hear it against the noise, during playback.

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 312 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 313 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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:

• 1 board (16 MHz ATmega328)


• 1 BMP180 sensor module
• 1 (Micro)SD card module
• 1 push-button
• wires or jumpers
• 1 (Micro)SD card, max. 32 GB

Construction is quite simple. In addi-


Fig. 13.9c: Connection diagram for BMP180
tion to the BMP180 sensor, we need sensor, SD card module, switch, and Arduino
to connect the SD card module, as
well as the push-button for starting and stopping the recording. Soldered wires are
always best, but we can also use jumpers. Then, we need two VCC lines, but the
Pro Mini has only one VCC pin. There’s a very simple solution: Since the BMP180
consumes hardly any power, we can also supply it using one output (e.g. with Pin 9
instead of VCC). Then, we only have to define the pin as an output in setup() and
switch it to HIGH.

317

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 314 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Sketch: Infrasound recorder 13_9.ino

#include <Wire.h> // library for I2C interface


#include <SPI.h> // library for SPI interface
#include <SD.h> // library for SD card

#define record_pin 2 // switch pin to ground for recording


#define low_pin 3 // Output pin will be set to LOW (used as GND for button)
[..]

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.

char oversampling = 0; // 0-> 1x , 1 -> 2x , 2 -> 4x , 3 -> 8x


long audio_rate = 44100; // sampling rate of the resulting audio file
unsigned long max_file_size = 105840044; // 10 minutes playback time (at 44100 Hz)

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 315 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 316 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

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.

void recording() // recording function


{
if (must_record && file_size < max_file_size) // if recording should run
{
if (!is_recording) // if recording is not running yet
{

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 317 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

do // search for the first not yet used recording number


{
file_number++; // next number (starting with 1)
} // repeat with next number while file already exists:
while (SD.exists("FILE_" + String(file_number) + ".wav"));
Serial.println("Recording no." + String(file_number) + " is started!");
wave_file = SD.open("FILE_" + String(file_number) + ".wav", FILE_WRITE);
if (!wave_file) // if file could not be opened
{
Serial.println("Error: File could not be opened!");
while (true); // do not continue (infinite loop)
}

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.

wave_file.print("RIFF"); // header WAV file format


wave_file.write(255); // later file size - 8 (4 bytes);
wave_file.write(255);
wave_file.write(255);
wave_file.write(255);
wave_file.print("WAVEfmt "); // identifiers
wave_file.write(16); // remaining length of the fmt block (4 bytes)
wave_file.write(byte(0));
wave_file.write(byte(0));
wave_file.write(byte(0));
wave_file.write(1); // data format (2 bytes, 1 -> PCM)
wave_file.write(byte(0));
wave_file.write(2); // number of channels (2 bytes)
wave_file.write(byte(0));

wave_file.write(audio_rate & 255); // sample frequency (4 bytes)


wave_file.write((audio_rate >> 8) & 255);
wave_file.write((audio_rate >> 16) & 255);
wave_file.write(audio_rate >> 24); // highest significant byte

wave_file.write((audio_rate << 2) & 252); // bytes per second...


wave_file.write((audio_rate >> 6) & 252); // ... (4 bytes) ...
wave_file.write((audio_rate >> 14) & 252); // ... 4 * sample rate
wave_file.write((audio_rate >> 22) & 252); // highest significant byte

wave_file.write(4); // bytes per frame (2 bytes, 16 bit stereo -> 4)


wave_file.write(byte(0));
wave_file.write(16); // resolution, number of bits (2 bytes)
wave_file.write(byte(0));

321

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 318 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

wave_file.print("data"); // begin of the data block


wave_file.write(255); // later file size - 44 (4 bytes)
wave_file.write(255);
wave_file.write(255);
wave_file.write(255);

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.

file_size = wave_file.position(); // update file size


if (file_size == 44) // check file size
{
Serial.println("OK, recording in progress!"); // recording confirmation
is_recording = true; // set variable for running recording
}
else // if file size is not correct
{
Serial.println("Error: Writing to file not possible!"); // message
wave_file.close(); // close file
while (true); // do not continue (infinite loop)
}
}

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.

else // if recording is already running


{
wave_file.write(record_value & 255); // pressure value, LSB first
wave_file.write(record_value >> 8); // HSB
wave_file.write(temp_value & 255); // temperature value, LSB first
wave_file.write(temp_value >> 8); // HSB
file_size += 4; // update file size
}
}

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 319 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

else // if recording is switched off


{
if (is_recording) // if recording is still running
{
if (wave_file.position() != file_size) // if file size is not correct
{
Serial.println("Warning: File size is not correct!");
}

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.

if (wave_file.seek(4)) // if position can be called


{
file_size -= 8; // update file size - 8 from byte 4 on
wave_file.write(file_size & 255); // LSB first
wave_file.write((file_size >> 8) & 255);
wave_file.write((file_size >> 16) & 255);
wave_file.write(file_size >> 24); // highest significant byte last
file_size += 8; // set file size back to full value
}
else Serial.println("Warning: Indication of file size not possible!");
if (wave_file.seek(40)) // if position can be called
{
file_size -= 44; // update file size - 44 from byte 40 on
wave_file.write(file_size & 255); // LSB first
wave_file.write((file_size >> 8) & 255);
wave_file.write((file_size >> 16) & 255);
wave_file.write(file_size >> 24); // highest significant byte last
file_size += 44; // set file size back to full value
}
else Serial.println("Warning: Indication of file size not possible!");

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 320 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

wave_file.seek(file_size); // pointer to end of file


wave_file.close(); // close file
Serial.println("Recording finished!"); // display message
is_recording = false; // no running recording
file_size = 0; // reset file size
}
else // recording was already finished
{
// nothing to do (wait for next recording)
}
}

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.

if (!digitalRead(record_pin) != pressed) // button just pressed or released


{
if (!pressed) // if button is just pressed (not released)
{
must_record = !must_record; // start or stop recording depending on state
pressed = true; // consider button as pressed
state_counter = 0; // reset counter
}
else // when pushbutton has just been released
{
state_counter++;
if (state_counter > 20) // debounce
{
pressed = false; // consider button as released
}
}
}
}
[...]

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 321 2022-07-27 20:20


Chapter 13 • Clever hacks and tricks

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 322 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

326

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 323 2022-07-27 20:20


Index

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 324 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Common cathode.................................... 230 F


Common-collector circuit......................117 Fake............................................................... 32
Compass.................................................... 223 Fake batteries.............................................40
Components................................................41 False.............................................................. 60
Connector..................................... 24, 44, 52 FAT32.......................................................... 256
Conrad Electronic...................................... 27 Feedback loop.......................................... 116
Constant current source....................... 116 File..................................................... 256, 315
Constants..................................................... 67 Flicker............................................... 108, 232
Construction template.............................88 Float..................................................... 60, 239
Cooling plate.................................. 118, 133 Floor.............................................................. 69
Cosine......................................................... 111 For loop......................................................... 90
CP2102......................................................... 21 Frame rate................................................. 232
Current measurement..............................76 Fraud............................................................. 32
Current source......................................... 155 Free-Running mode......................267, 286
D Freewheeling diode...................... 105, 164
Data............................................................ 256 Frequency................................ 16, 108, 315
DC motor................................................... 163 Full-step operation................................. 172
DC voltage................................................. 114 Functions......................................................54
Debouncing.......................................64, 324 G
Default reference....................................... 65 Gearbox..................................................... 174
Define............................................................ 67 Gold-plated contacts.............................. 153
Define characters.................................... 253 Gravity........................................................ 219
Degree....................................239, 244, 247 GY-87.......................................................... 227
Delay().......................................................... 91
H
Dice............................................................. 275
H-bridge..................................................... 165
Die............................................................... 275
Half-step operation................................. 172
Digit................................................... 230, 232
HC-SR04.................................................... 201
DigitalRead()............................................... 61
HC-SR501.................................................. 205
DigitalWrite()..............................................61
Header........................................................ 322
Dimming.......................................... 108, 109
Heat conductive adhesive.................... 104
Direction.......................................... 163, 165
Heat shrink tubing....................................24
Display.............................................. 230, 250
Heat sink................................104, 118, 133
Distance sensor....................................... 201
Heat test.................................................... 102
Do-while loop..............................................63
Hex.............................................................. 239
DT-830.......................................................... 45
Hexadecimal............................................. 244
DTR connection.......................................... 22
HIGH............................................... 58, 60, 61
Duty cycle........................................ 108, 114
HMC5883L................................................. 223
E Hole matrix..................................................49
E12 series.................................................... 41 Hot glue gun............................................... 36
Earth’s magnetic field............................ 223
I
Ebay............................................................... 28
I²C functions............................................ 210
Echo............................................................ 201
I²C interface................................... 207, 254
EEPROM..............................................15, 277
I²C sensor................................................. 211
Effect................................................... 86, 255
IDE......................................................... 53, 56
Electrolytic capacitor................................42
If..................................................................... 62
Electromagnet.......................................... 106
IIC................................................................ 207
Else................................................................ 62
Inclination................................................. 223
Emitter-follower....................................... 117
Infrared receiver........................... 199, 291
Endless loop.............................................. 258
Infrared remote............................. 199, 291
ESC.............................................................. 189

328

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 325 2022-07-27 20:20


Index

Infrared thermometer.............................. 45 MOSI........................................................... 256


Infrasound................................................. 315 Motion sensor...........................................205
Input...................................................... 58, 61 Motor...........................................................163
Int........................................................ 60, 239 MPU-6050..................................................219
Internal reference..................................... 66 Multi-sensor..............................................227
Internal resistance........................127, 152 Multimeter................................................... 45
J Multiplexing.............................................. 230
Joystick...................................................... 198 N
Jumper....................................................... 166 N-channel MOSFET................................. 102
Jumper wires.............................................. 44 Nano board.................................................. 20
L NiCd...................................................155, 162
NiMH..................................................155, 162
L298N......................................................... 166
L9110S....................................................... 165 NTC.................................................... 194, 246
LDR.............................................................. 193 Number......................................................... 60
Lead-acid batteries.......................155, 162 O
LED......................................................... 43, 83 On-board LED............................................. 82
LED effect.................................................... 92 Open collector.......................................... 207
LED strip.................................................... 111 Optocoupler.............................................. 107
Level.......................................... 58, 114, 265 OR................................................................ 119
Library........................................................ 209 Output............................................58, 61, 82
LilyPad........................................... 19, 56, 88 Oversampling.................................214, 308
LiquidCrystal.h......................................... 252 P
Lithium polymer batteries....................260 PayPal............................................................ 31
Lithium-ion batteries......................39, 260 PCB layout................................................. 128
Long............................................................... 60 PCM............................................................. 315
Loop() function........................................... 54
Perfboard...................................................... 49
LOW................................................ 58, 60, 61 Photodiode................................................ 199
Low-battery.............................................. 271 Pin header............................................ 44, 52
Low-pass filter......................................... 114 Pin layout..................................................... 17
M PinMode()..................................................... 61
Magnetic field sensor............................. 223 Pixel............................................................. 250
MAh......................................... 127, 152, 155 Plug-in board.............................................. 47
Mail order companies............................... 27 Point-to-point construction.....................48
Master............................................... 208, 210 Potentiometer........................................... 169
Max().......................................................... 168 Power dissipation..........................118, 160
Measuring range.............................. 65, 244 Power LED.................................................... 98
Measuring tool............................................ 45 Power loss.................................................... 78
Memory card............................................ 256 Power supply....................................... 37, 38
Micro USB.......................................... 26, 264 Power-down..............................................269
Microcontroller.................................... 14, 16 Pressure graph.........................................315
MicroSD...................................................... 256 Primary colors..........................................110
Microstepping...........................................183 Print()......................................................... 258
Milliampere hour........................... 127, 152 Pro Mini......................................................... 14
Min()........................................................... 168 Processor.............................................. 14, 56
Mini lasers................................................. 254 Program memory....................................... 15
MISO........................................................... 256 Programming.............................................. 53
Modulation frequency............................ 292 Programming language........................... 53
Modulo........................................................ 239 Protection circuit..............................41, 262
MOSFET......................................................102 Pull-up resistor......................... 59, 61, 208

329

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 326 2022-07-27 20:20


Arduino & Co – Measure, Control, and Hack

Pulse................................................. 108, 201 Serial.println............................................... 68


Pulse duration.......................................... 202 Series resistor............................................ 84
Pulse-width modulation...............108, 164 Servo.......................................................... 192
PulseIn().................................................... 202 Setup() function......................................... 54
Pulses......................................................... 190 Seven-segment display.........................230
Pushbutton........................................ 63, 324 Shipping costs............................................ 30
PWM.................................................. 108, 164 Shunt............................................................. 76
R Shut down................................................. 268
Radio frequency....................................... 228 Sine............................................................. 111
RAM............................................................... 15 Sketch................................................... 54, 55
Random number..............................94, 280 Slave........................................................... 210
RAW............................................................... 16 Sleep mode............................................... 269
Sleep.h....................................................... 268
Rechargeable batteries......................... 260
Recorder.................................................... 315 SMD..................................................... 50, 103
Reference voltage......................................65 Socket................................................... 44, 52
Refresh rate.............................................. 233 Socket header............................................ 52
Register............................................ 208, 266 Solder............................................................ 34
Soldering...................................................... 24
Reichelt Elektronik....................................28
Relay........................................................... 106 Soldering iron...................................... 34, 35
Remote control.................... 199, 228, 291 Solid-State relay..................................... 107
Remote control receiver........................291 Speed.......................................................... 164
Residual ripple......................................... 114 Speed of sound........................................ 201
SPI bus......................................................... 18
Resistor......................................................... 41
Resistor measurement.............................80 SPI interface............................................. 256
RF remote control................................... 228 SPI.h........................................................... 257
RGB................................................... 105, 109 Spirit level................................................. 219
Ripple................................................ 114, 115 SSR.............................................................. 107
Standby...................................................... 268
Rotation force........................................... 220
Rotor................................................. 170, 173 Stepper...................................................... 170
Round............................................................ 69 Stripboard....................................................50
Round cells...................................... 260, 261 Switch......................................................... 106
Rounding...................................................... 69 Switch-off.................................................. 271
Switching..................................................... 82
RS-232.......................................................... 21
Running light effect.................................. 94 Syntax........................................................... 54

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 327 2022-07-27 20:20


Index

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

arduino_and_co_DM 2022-07-27 CROPMARKS.pdf 328 2022-07-27 20:20


books
books books

Arduino & Co Arduino & Co

Arduino & Co – Measure, Control, and Hack • Robert Sontheimer


Measure, Control, and Hack
Clever Tricks with
ATmega328 Pro Mini Boards
Measure, Control, and Hack
With a simple Pro Mini board and a few other components, projects that Clever Tricks with
ATmega328 Pro Mini Boards
20 or 30 years ago were unthinkable (or would have cost a small fortune)
are realized easily and affordably in this book: From simple LED effects #define measure_pin A3 //
to a full battery charging and testing station that will put a rechargeable Robert Sontheimer was Measuring input (A0 and A1 us
through its paces, there’s something for everyone. immediately on board when the ed
first home computers arrived in float r_ntc25 = 50
our living rooms some 40 years 000; // resistance
All the projects are based on the ATmega328 microcontroller, which offers ago, with his ZX81 and C64. Back float r_fixed = 47 of the NTC at 25
endless measuring, switching, and control options with its 20 input and then, he converted a plotter into a 000; // fixed resi d
float r_factor[33] stor (example 47
output lines. For example, with a 7-segment display and a few resistors, scanner, among other quirky and
= // R-factor at ki
you can build a voltmeter or an NTC-based thermometer. The Arduino original ideas, and today he uses
5°) NTC version 39 -30 to +130 degree
platform offers the perfect development environment for programming
Arduino Pro Minis to control entire
50
CNC laser machines and has even
{17.3, 12.8, 9.59,
this range of boards. invented a matching suction system:
7.25, 5.51, 4.23,
his “self-changing toilet paper filter.” 0.81, 0.656, 0.535, 3.27, 2.54, 1.99,
Besides these very practical projects, the book also provides the necessary In his office, he has a magnet that’s 0.438, 0.36, 0.3, 1.
been levitating for years – controlled 0.107, 0.091, 0.07 0.25, 0.21, 0.176,
knowledge for you to create projects based on your own ideas. How to
79, 0.0671, 0.0585 0
, 0.0507, 0.0441,
by a Pro Mini, of course.
measure, and what? Which transistor is suitable for switching a certain Just a regular freak. 0.0294}; 0.03
load? When is it better to use an IC? How do you switch mains voltage?
Even LilyPad-based battery-operated projects are discussed in detail, as
well as many different motors, from simple DC motors to stepper motors. byte digits = 4;
// number of digi
boolean common_ano ts
Sensors are another exciting topic: For example, a simple infrared receiver
de = false; // fa
that can give disused remote controls a new lease on life controlling your byte segment_pin[ lse for common ca
home, and a tiny component that can actually measure the difference in 8] = {11,A1,6,8,9 thod
byte digit_pin[6] ,12,5,7}; // a,b,
air pressure between floor and table height!
= {4,A0,13,10}; // c,d,e
last to first digi
t
int frequency = 20
0; // frequency of
float change_frequ display in Hz (abo
ency = 0.5; // re ut
byte segments[20] fresh display ever
= // bit pattern y 2
Elektor International Media BV
(a to g) for the di
www.elektor.com ffer
B1111110, B0110000
, B1101101, B11110
B1011011, B1011111 01, B0110011, //
, B1110000, B11111 0
B1110111, B0011111 11, B1111011, //
,Robert
B100Sontheimer
1110, B0111101, B1 5,
001111, // A,

You might also like