last updated: 20220507
For students:
Beginning with this chapter we will come across many "Just do it" tasks. These tasks have to be carried out and have to be documented thoroughly!
Your marks will depend on the documentation!
To better understand parts of this module, first work through the module electronics fundamentals ELEFU
.
Song of this chapter: Kraftwerk > Computerwelt > Nummern
We live in a digital world. Digital comes from digit or digiti in Latin meaning "fingers". Fingers are used to count and nine of them correspond for us to the ten digits 09 (symbols) of the decimal system.
If we use numbers we quantize a value, making the step from analog (continuous) to digital (sequence of discrete values). Computers need numbers. We will see later in this course how analog signals are converted into digital signals.
So why did digital electronics won over analog electronics?
Analog signals are subject to electronic noise and distortion which can progressively degrade the signal.
With the increasing speed of digital circuits and their miniaturization the analog world didn't stand a chance in most applications.
OK, it's not the most tingling subject to begin with, but without a good understanding how digital systems calculate and use only two states (high voltage and low voltage) to do all their job, you will not really progress if problems occur in your projects (and they will occur!). So let' begin at the beginning:
We have ten fingers, to count from one to ten, an unary numeral system in which every natural number is represented by a corresponding number of symbols (e.g fingers). After the invention of the new symbol Zero
(0
), it was possible to develop a positional numeral system because zero is of crucial importance to be able to "skip" a power.
Today we use a base10 numeral system called decimal system with 10 digits from zero to nine.
Mankind had other possibilities! If we would use our fingers in a binary positional number system with each finger as a position we could count from zero to 1023
. Or why don't we use today a base20 (vigesimal, fingers and toes) numeral system like in the Mesoamerican Long Count calendar from the Maya?
In a positional base b numeral system we get b basic digits (symbols).
As zero is included, b1 natural numerals are used.
Base  digits 

2 
01 
8 
07 
10 
09 
16 
09 + AF 
Numeral systems with different bases can have sometimes funny names like Quaternary, Nonary, Duodecimal, Tritridecimal or Duosexadecimal. For a list look here.
The binary
(base 2) and hexadecimal
(base 16) systems are, extensively used in computer science. Sometimes also the octal (base 8) system. The binary system uses only the digits 0
and 1
. The hexadecimal system uses all the digits from the decimal system, plus the letters A
through F
(decimal numerals 10 to 15).
A numeral is a sequence of digits, which may be of arbitrary length. Each position in the sequence has a decimal place value. Place values for different bases:
Base  Place  

b  6  5  4  3  2  ones place 
Base  Values  
b 
b^{5} 
b^{4} 
b^{3} 
b^{2} 
b^{1} 
b^{0} 
2  32  16  8  4  2  1 
8  32768  4096  512  64  8  1 
10  100000  10000  1000  100  10  1 
16  1048576  65536  4096  256  16  1 
The place value can be calculated with the base:
place value = b^{(place number1)}
The value of the numeral is calculated by multiplying each digit's value in the sequence by its place value, and summing the results. Let's see some examples:
Base  example  

2  1101  =  1·8  +  1·4  +  0·2  +  1·1  =  13_{(10)} 
8  1101  =  1·512  +  1·64  +  0·8  +  1·1  =  577_{(10)} 
10  1101  =  1·1000  +  1·100  +  0·10  +  1·1  =  1101_{(10)} 
16  1101  =  1·4096  +  1·256  +  0·16  +  1·1  =  4353_{(10)} 
For mathematicians a number is a count that is an idea in our minds. A numeral is a symbol or name that stands for a number. In short: the number is an idea, the numeral is how we write it. In normal language, the words number and numeral are used for the same thing. But we use always the word digit when talking about the single symbols that make up numerals (Ziffer und Zahl).
Because we don't want to confound numbers of different numeral systems, it's important to use subscribed parentheses with the base included:
1001_{(2)} = 9_{(10)}, 1001_{(2)} ≠ 1001_{(10)}
.
There exist also positions for numbers less than 1
, so that floats are no problem.
Base  Place  

b  3  2  1  1  2  3 
Base  Values  
b 
b^{2} 
b^{1} 
b^{0} 
b^{1} 
b^{2} 
b^{3} 
2  4  2  1  1/2  1/4  1/8 
16  256  16  1  1/16  1/256  1/4096 
Calculate by hand (note the calculus) the values in decimal for the following numbers:1010011_{(2)}
, 4567_{(8)}
, 53_{(16)}
, BABA_{(16)}
.
Calculate by hand (note the calculus) the values in decimal for the following numbers:11100,11_{(2)}
, 1100001100,001_{(8)}
, 7B3A,4_{(16)}
.
Read the clock (each column is a binary number hh:mm:ss) and note the result.
Computers and electronics have no fingers but another way to represent the state of anything: high voltage and low (or no) voltage (current, field).
So all calculations and manipulations of computers rely on a base2 number system, the binary numeral system.
In binary we have the two symbols: 0
and 1
. But even by using only two symbols we can create any number that a decimal system can.
The basic unit of information used in computing and digital communications is the bit
(BInary digiT). The bit can only have one of two values. To work with bits we use twostate devices like per example a switch.
The unit symbol for the bit is bit
.
The two physical states of a binary digit represented by a device can be interpreted as logical values
(true
/false
, yes
/no
), activation states (on
/off
) or any other twovalued attribute.
The assignment is a matter of convention, and different assignments may be used even within the same device or program.
In our image the convention is that of the positive logic
, meaning the higher voltage level represents the value 1
and the lower voltage level represents the value 0
.
negative logic
(look in "Special voltage dividers: circuits with pullup or pulldown resistors" in ELEFU
chapter Kirchhoff).Any number can be represented by a sequence of bits. As seen above we use a positional system. The greater the number the more positions we need. As the number of positions in a binary system can get very high, the hexadecimal system (base 16) is often used by humans to ease the handling.
Binary 
Hex.  Dec.    Binary 
Hex.  Dec.    Binary 
Hex.  Dec.    Binary 
Hex.  Dec. 

0 
0  0    10000 
10  16    100000 
20  32    110000 
30  48 
1 
1  1    10001 
11  17    100001 
21  33    110001 
31  49 
10 
2  2    10010 
12  18    100010 
22  34    110010 
32  50 
11 
3  3    10011 
13  19    100011 
23  35    110011 
33  51 
100 
4  4    10100 
14  20    100100 
24  36    110100 
34  52 
101 
5  5    10101 
15  21    100101 
25  37    110101 
35  53 
110 
6  6    10110 
16  22    100110 
26  38    110110 
36  54 
111 
7  7    10111 
17  23    100111 
27  39    110111 
37  55 
1000 
8  8    11000 
18  24    101000 
28  40    111000 
38  56 
1001 
9  9    11001 
19  25    101001 
29  41    111001 
39  57 
1010 
A  10    11010 
1A  26    101010 
2A  42    111010 
3A  58 
1011 
B  11    11011 
1B  27    101011 
2B  43    111011 
3B  59 
1100 
C  12    11100 
1C  28    101100 
2C  44    111100 
3C  60 
1101 
D  13    11101 
1D  29    101101 
2D  45    111101 
3D  61 
1110 
E  14    11110 
1E  30    101110 
2E  46    111110 
3E  62 
1111 
F  15    11111 
1F  31    101111 
2F  47    111111 
3F  63 
The length of a binary number may be referred to as its bitlength.
As seen above the value of the numeral is calculated by multiplying each digit's value in the sequence by its place value, and summing the results.
Base  example  

2  1101  =  1·8  +  1·4  +  0·2  +  1·1  =  13_{(10)} 
16  1101  =  1·4096  +  1·256  +  0·16  +  1·1  =  4353_{(10)} 
One method to to convert a decimal number to binary is repeatedly dividing the decimal number by 2
, until it is reduced to zero. Every time we divide the remainder of the division becomes a digit in the binary number. To get the remainder is easy because we are dividing by two: If the dividend is even, the remainder will be 0 and if the dividend is odd the remainder is 1.
Example converting 187_{(10)}
to binary:
remainder  direction to read  

187 
/ 2 = 
93 
1 
⇧ 
93 
/ 2 = 
46 
1 
⇧ 
46 
/ 2 = 
23 
0 
⇧ 
23 
/ 2 = 
11 
1 
⇧ 
11 
/ 2 = 
5 
1 
⇧ 
5 
/ 2 = 
2 
1 
⇧ 
2 
/ 2 = 
1 
0 
⇧ 
1 
/ 2 = 
0 
1 
⇧ 
187_{(10)} = 10111011_{(2)}
By grouping 4 bits we get the hexadecimal number:
1011 1011_{(2)} = BB_{(16)} = 11·16 + 11 = 187_{(10)}
.
The same dividing method used for binary numbers can be used to convert decimal numbers to hexadecimal numbers:
Example converting 2019_{(10)}
to hexadezimal:
remainder  direction to read  

2019 
/ 16 = 
126 
3 
⇧ 
126 
/ 16 = 
7 
14 
⇧ 
7 
/ 16 = 
0 
7 
⇧ 
2019_{(10)} = 7E3_{(16)}
Calculate by hand (note the calculus) the binary value of 2019_{(10)}
.
As stated above, we get the hexadecimal number by grouping 4 bits to one hexadecimal digit beginning with the least significant 4 bits. Get the hexadecimal number of the binary equivalent of 2019_{(10)}
by grouping 4 bits beginning with the lowest bits. Compare the result with the example above.
Summand + Summand = Sum
Addend + Addend = Sum
Rules:
S1  +  S2  =  Sum  Carry (C) 

0 
+  0 
=  0 
0 
0 
+  1 
=  1 
0 
1 
+  0 
=  1 
0 
1 
+  1 
=  0 
1 
S1  +  S2  +  Ci  =  Sum  Carry (C) 

1 
+  1 
+  1 
=  1 
1 
Ci stands for Carry in.
Examples:
1011_{(2)} 
+ 
control in decimal:  11_{(10)} 
+ 

10011_{(2)} 
19_{(10)} 

11 
C 
1 
C 

 
 

11110_{(2)} 
30_{(10)} 
1111_{(2)} 
+ 
control in decimal:  15_{(10)} 
+ 

1111_{(2)} 
15_{(10)} 

10101_{(2)} 
21_{(10)} 

1 

1 11 
C 
1 
C 

 
 

110011_{(2)} 
51_{(10)} 
To build a binary adder we need a digital adder circuit. We will learn about such a circuit in the next chapter.
Minuend  Subtrahend = Difference
Rules:
M    S  =  Difference  Borrower (B) 

0 
  0 
=  0 
0 
0 
  1 
=  1 
1 
1 
  0 
=  1 
0 
1 
  1 
=  0 
0 
M    S1    S2  =  Difference  Borrower (B) 

0 
  1 
  1 
=  0 
1 
1 
  1 
  1 
=  1 
1 
This rules are only valid if we subtract a smaller number from a bigger number (Minuend ≥ Subtrahend)). If the Subtrahend is bigger we exchange Subtrahend and Minuend and add a minus sign to the result.
Examples:
11101_{(2)} 
 
control in decimal:  29_{(10)} 
 

1010_{(2)} 
10_{(10)} 

1 
B 
B 

 
 

10011_{(2)} 
19_{(10)} 
111000111_{(2)} 
 
control in decimal:  455_{(10)} 
 

101001011_{(2)} 
331_{(10)} 

1111 
B 
B 

 
 

001111100_{(2)} 
124_{(10)} 
One drawback of direct subtraction is that borrowing frequently over many positions is tedious and confusing. As our processor has only digital adders it's by ways more convenient to use a method doing the subtraction by adding the bases complement of the subtrahend (this can be done in all positional numeral systems: e.g tenth complement in a decimal system).
To get the complement the easiest way is often to get b1complement and add the number one (1) afterwards. This gets very easy in a binary system because we get the b1complement (2  1 = 1: one's complement) by inverting the subtrahend. Before doing this, the subtrahend must be stuffed with leading zeros to match the length of the minuend!
To get the two's complement we add the number one to the one's complement. In binary systems the one's complement is calculated by inverting the number!
Examples:
Subtrahend  One's complement  Two's complement  

11101_{(2)} 
00010_{(2)} 
+  1  =  00011_{(2)} 
0000100_{(2)} 
1111011_{(2)} 
+  1  =  1111100_{(2)} 
Another advantage of the complements method is that the minuend can be lesser then the subtrahend which is not possible in the direct method.
Rules:
Examples:
We use the same examples as in the direct subtraction:
29_{(10)}  10_{(10)} = 19_{(10)}
stuffed  1's complement  2's complement  

11101_{(2)} 
 

1010_{(2)} 
01010_{(2)} 
10101_{(2)} 
10110_{(2)} 

11101_{(2)} 
+ 

10110_{(2)} 

1 
11 
C 
positive 

 

10011_{(2)} 
455_{(10)}  331_{(10)} = 124_{(10)}
1's complement  2's complement  

111000111_{(2)} 
 

101001011_{(2)} 
010110100_{(2)} 
010110101_{(2)} 

111000111_{(2)} 
+ 

010110101_{(2)} 

1 
11 111 
C 
positive 

 

001111100_{(2)} 
In both examples a carry occurred, so the results are positive. Let' try the last example by changing minuend and subtrahend, so that the minuend is lesser:
331_{(10)}  455_{(10)} = 124_{(10)}
1's complement  2's complement  

101001011_{(2)} 
 

111000111_{(2)} 
000111000_{(2)} 
000111001_{(2)} 

101001011_{(2)} 
+ 

000111001_{(2)} 

0 
1111 11 
C 
negative 

 

110000100_{(2)} 
No carry, so the result is negative. We get the absolute value with the two's complement of the result:
Result: 110000100_{(2)}
One's complement: 001111011_{(2)}
Two's complement: 001111100_{(2)} = 124_{(10)}
To build a binary subtraction circuit we need the digital adder from above and an inverter circuit.
Multiplicand · Multiplier = Product
Factor · Factor = Product
Rules:
Md  ·  Mr  =  Product 

0 
·  0 
=  0 
0 
·  1 
=  0 
1 
·  0 
=  0 
1 
·  1 
=  1 
This is the truth table of an AND
gate. The AND
gate is a one bit multiplier circuit.
AND
with the bits of the multiplier beginning with the least significant bit (2^{0}
).AND
's) are shifted by one position to the left and added to the momentary result.Example:
11011_{(2)} 
· 
control in decimal:  27_{(10)} 
· 

1101_{(2)} 
13_{(10)} 

 
 

11011 
81 

00000 
27 

11011 
1 
C 

11011 
 

11111 
C 
351_{(10)} 

 

101011111_{(2)} 
To build a binary multiplier we need AND
gates, shift registers and adder circuits.
Dividend / Divisor = Quotient
Numerator / Denominator = Quotient
The division is a successive subtraction.
Rules:
Example:
Let's do the following division: 1011101,11_{(2)} / 110_{(2)}
.
The two's complement of the divisor is 001_{(2)}+1 = 010_{(2)}
.
If we do a subtraction with 3 positions and 1001_{(2)}+1 = 1010_{(2)}
if we do a subtraction with 4 positions:
1011101,110_{(2)}/ 
110_{(2)} = 1111,101_{(2)} 
decimal control:  93,75_{(10)} / 6_{(10)} = 15,625_{(10)} 

010  
6 

  
 


C 
no carry n+1 : discard 
0 ⇩ 
33 

1011  
30 

1010  
 

1 1   
C 
carry n+1 : positive 
1 ⇩ 
3 7 

  
3 6 

01011  
 

1010  
15 

1 1   
C 
carry n+1 : positive 
1 ⇩ 
12 

  
 

01010  
30 

1010  
30 

1 1   
C 
carry n+1 : positive 
1 ⇩ 
 

  
0 

01001  

1010  

1  
C 
carry n+1 : positive 
1 ⇩ 

  

0011 1 

101 0 

111  
C 
carry n+1 : positive 
1 ⇩ 

 

000 11 

10 10 

0 1  
C 
no carry n+1 : discard 
0 ⇩ 

 



0 110 

1 010 

11 1 
C 
carry n+1 : positive 
1 ⇩ 

 

0 000 
To do a binary division we need shift registers, adders and inverters.
The rules are the same as in decimal calculations. Here two examples:
Addition
ABC_{(16)} 
+ 
control in decimal:  2748_{(10)} 
+ 

983_{(16)} 
2435_{(10)} 

11 
C 
1 1 
C 

 
 

143F_{(16)} 
5183_{(10)} 
Subtraction
ABC_{(16)} 
 
control in decimal:  2748_{(10)} 
 

9F3_{(16)} 
2547_{(10)} 

1 
B 
B 

 
 

0C9_{(16)} 
201_{(10)} 
Multiplication and Division
As child we learn the decimal multiplication table by heart. To facilitate the hex multiplication you can use a table for hexadecimal multiplication (Learn it by heart ;)).
The division works the same as for decimal numbers.
It's easier to do the calculations in binary and convert the results in hexadecimal numbers.
To do the four basic arithmetic operations we need:
All these circuits are implemented in the Arithmetic Logic Unit (ALU
) of modern processors and controllers.
37_{(10)} + 425_{(10)}
4477_{(10)}  425_{(10)}
121_{(10)} · 425_{(10)}
4477_{(10)} / 37_{(10)}
.Digital circuits, processors and microcontroller are often built to work with a fixed length of bits. First processors worked with 4 bits called a nibble
. A nibble
is half a byte and big enough to hold a BinaryCoded Decimal (BCD
, digits 09). A nibble
is also one digit in hexadecimal!
The byte
was historically the number of bits used to encode a single character of text in a computer. Often it was also the smallest addressable unit of memory in many computer architectures. As no standards existed until 1993, the byte could have different length (e.g. six bit or 9 bit).
The international standard IEC 8000013 defined as unit symbol for the byte the uppercase character B
.
A byte has 8 bit permitting the values 0 through 255 (2^{8} = 256
).
A multiple of the unit byte is not the kilobyte, because kilo
is defined as Thousand (1000 decimal) and does not fit in the binary system. For quantities of digital information, the binary prefix Ki(kibi)
(bi for binary!) was defined and is part of part of the International System of Quantities.
Kibi
means 2^{10}
, or 1024
.
One kibibyte is 1024 bytes and the unit symbol for the kibibyte is KiB.
Other binary prefixes are mebi
, gibi
and tebi
.
binary prefix  unit symbol  value  difference to decimal in % 

kibi  KiB  2^{10} = 1024  2.4 
mebi  MiB  2^{20} = 1024^{2} = 1048576  4.9 
gibi  GiB  2^{30} = 1024^{3} = 1073741824  7.4 
tebi  TiB  2^{40} = 1024^{4} = 1099511627776  10 
pebi  PiB  2^{50} = 1024^{5} = 1125899906842624  12.6 
exbi  EiB  2^{60} = 1024^{6} = 1152921504606846976  15.3 
If we find indications in kB
(MB
,GB
) we calculate with Thousand.
A harddisk with 6TB
has 6,000,000,000,000 byte or 5.457TiB
.
The word
is the piece of data used by by the instruction set respectively the hardware of a processor. The bitlength of the word (word length) is an important characteristic of the used processor or computer architecture.
The word size varies in modern processors and microcontrollers from 8 to 64 bits (steps of 8 bits).
As we often use fixed length (mostly multiple of 1 byte) to store data in computers, leading zeros are necessary for numbers with less bits. E.g. the number 101_{(2)}
will be written 00000101_{(2)}
if we use one byte.
Leading zeros are not always required but they do help by showing the bitlength of the number.
Negative numbers are created with the two’s complement (see above). The highest bit (the "signbit"), is needed to flag the number as a negative number. To get the two’s complement, the rest of the bits are inverted and 1
is added. As the highest bit is no longer available, the maximal positive number we can get with a byte is only 127_{(10)}.
bytes  range unsigned  range signed 

1 
0 to 255_{(10)} (2^{8}1) 
128_{(10)} to 127_{(10)} 
2 
0 to 65535_{(10)} (2^{16}1) 
32768_{(10)} to 32767_{(10)} 
4 
0 to 4294967295_{(10)} (2^{32}1) 
2147483648_{(10)} to 2147483647_{(10)} 
Test the following program on 3 different Arduino platforms (e.g. Teensy 2.0 or Arduino Uno, Arduino Due, ESP8266, ESP32). Make a table in calc or excel to compare the values.
// jdinc5_test_data_types.ino
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("Numbers of bytes for:");
Serial.println(" unsigned whole numbers ");
Serial.println("bool: " + String(sizeof(bool)));
Serial.println("byte: " + String(sizeof(byte)));
Serial.println("uint16_t: " + String(sizeof(uint16_t)));
Serial.println("word: " + String(sizeof(word)));
Serial.println("unsigned int: " + String(sizeof(unsigned int)));
Serial.println("unsigned long: " + String(sizeof(unsigned long)));
Serial.println("unsigned long long: " + String(sizeof(unsigned long long)));
Serial.println(" signed whole numbers ");
Serial.println("char: " + String(sizeof(char)));
Serial.println("short: " + String(sizeof(short)));
Serial.println("int: " + String(sizeof(int)));
Serial.println("long: " + String(sizeof(long)));
Serial.println("long long: " + String(sizeof(long long)));
Serial.println(" floating point numbers ");
Serial.println("float: " + String(sizeof(float)));
Serial.println("double: " + String(sizeof(double)));
Serial.println(" special ");
Serial.println("size_t: " + String(sizeof(size_t)));
}
void loop() {}
As seen, binary is what drives all electronics. The electronic circuit knows only two states. An important point is the storing of the states, respectively our information. In the image above the information (one byte) is stored mechanically in the switches.
In computers we need a circuit capable of storing the information. This can be done e.g. with one transistor and one capacitor for volatile dynamic randomaccess memory (DRAM) or with more transistors to build memory cells with bistable flipflops in static randomaccess memory (SRAM). This will be a chapter of it's own in this tutorial :).
Our circuits memorize states. The interpretation of this states is arbitrary! Let's look at an example. We take a double register of our Arduino (ATmega328), filled with the following ones and zeros:
1011101001100010_{(2)}
Test the following program. Open the serial monitor to watch the output. Look at the data types and interpret the different outputs. Try to figure out why the second character displayed (highbyte) is weird.
// jdinc6_test_numbers.ino
int i = 0B1011101001100010;
bool b = i;
unsigned int ui = i;
float f = i;
char sl = lowByte(i);
char sh = highByte(i);
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("bool:\t\t\t" + String(b));
Serial.println("unsigned int:\t\t" + String(ui));
Serial.println("unsigned int (bin):\t" + String(ui,BIN));
Serial.println("unsigned int (oct):\t" + String(ui,OCT));
Serial.println("unsigned int (hex):\t" + String(ui,HEX));
Serial.println("int:\t\t\t" + String(i));
Serial.println("float:\t\t\t" + String(f));
Serial.println("char low nibble:\t" + String(sl));
Serial.println("char high nibble:\t" + String(sh));
}
void loop() {}
We see that the same bits can mean a number, even in different numeral systems, or character(s) or an instruction of our programming language:
The flash of the microcontroller used for Arduino Uno (where the program is stored) is organized in words of 16 bit. If we put the same ones and zeros in the flash, they will be interpreted as a command line of a program written in machine code. In assembly language this would be the command out PORTD,r6
, to copy the byte of register number six to the eight pins of port D.
The assignment of a unique sequence of bits to the representation of each one of a set of numbers, letters or instructions is called a binary code (see next chapter).
While programming, it is important to use the correct data type! A wrong data type can give erratic results. E.g. the following instruction will provoke a faulty datarate for a serial communication:
int bit_rate = 115200;
Sometimes it's very difficult to find these errors, because they can show up very randomly. So think twice before using a data type.
All data needs to be stored, and the memory of microcontroller is very limited. Also the selection of how to store data makes an impact on performance. So select always the smallest variable that is large enough, and make the data type unsigned where suited. To hold e.g. a digital pin number in Arduino (054_{(10)}) an int
(32768_{(10)} to 32767_{(10)}) is overkill and can lead to errors. A byte is largely enough (0255_{(10)}).
The reference is here.
bool
occupies one byte of memory. 00000000_{(2)}
means false
, all other values mean true
. Boolean is a nonstandard type alias for bool. Use bool
instead.
byte
stores an 8bit unsigned number (0 to 255_{(10)}). Other types that mean the same are unsigned char
and uint8_t
. Use byte
or uint8_t
.
word
stores an unsigned number, same as unsigned integer
or uint16_t
. The lengths must not be the same. The length of uint16_t
is always 16 bit (0 to 65535_{(10)}). The lengths of word
and unsigned integer
depend on the architecture. Use the program from above if you need to get the length of your platform.
unsigned long
and uint32_t
are the same and can be used for big numbers (4 byte: 0 to 4294967295_{(10)}). If we use constants, we must add ul
or UL
to the number.
int
(integer) is normally our primary datatype for number storage. It is signed, so the highest bit is used to show if the number is negative (1
) or not (0
). On ATmega based boards (Uno, Teensy 2.0) an int
is a 16bit value with the number range from 32768_{(10)} to 32767_{(10)}. On other boards (Teensy 3.1, ESP8266, ESP32) we have a 32bit (4byte) value for int
(2147483648_{(10)} to 2147483647_{(10)}). If we want only 16 bit on all platforms we can use the type short
(seldom needed). The 8bit variant of integer is char
(128_{(10)} to 127_{(10)}) mostly used to store characters (see later).
long
is used for bigger integers (4 byte, 2147483648_{(10)} to 2147483647_{(10)}). If we use constants, we must add l
or L
to the number.
float
is the datatype for floatingpoint numbers (number with decimal point). Floatingpoint numbers can be much bigger than integers and are stored in 4 bytes. There range is from 3.4028235·10^{38}_{(10)} to 3.4028235·10^{38}_{(10)}, but they have only 67 decimal digits of precision. Floating point numbers are not exact! so pay attention when comparing float
numbers. Also the calculations with floating point numbers on the original Arduino platform (ATmega) are much slower than with integer and should be avoided (if needed use an ESP32 instead). Don't forget the decimal point, otherwise the number will be treated as an int
(see here for details).
char
is the data type that stores a character value (1 byte). Characters are stored as signed numbers, so it is possible to do math with them ('A'+1 = 'B'). There encoding can be found in the ASCII chart. Single quotes are used in C to assign characters (e.g. char big_b = 'B'
).
double
is normally the bigger float
with more precision (e.g. up to 15 digits) and 8 byte. This is not true on the original Arduino platform, where double is the same size as float. If you need double use an ESP8266, ESP32 or Teensy3.x).
long long
, unsigned long long
, and uint64_t
with 8 byte are defined in C but should not be used on the original Arduino platform, because of the restricted amount of memory.
String()
constructs an instance of the String class, so we use here object oriented programming (OOP). Because of the little memory of original Arduino boards, the improper use of this data type can make your sketches fragile and unstable. So use String
only if you know what you do and on bigger platforms (e.g. ESP32). Better use arrays of char
.
array
is a data type for a collection of variables that are accessed with an index number. For more information on arrays look in the Arduino array reference.
void
is only used in function declarations to indicate that the function does not return information to the function from which it is called.
Table with bytelength of data types
data type  Uno (T2.0)  Teensy 3.6  ESP8266  ESP32  

unsigned whole numbers:  
bool  1  1  1  1  
byte  1  1  1  1  
uint16_t  2  2  2  2  
word 
2 
2 
4 
4 

unsigned int 
2 
4 
4 
4 

unsigned long  4  4  4  4  
unsigned long long  8  8  8  8  
signed whole numbers:  
char  1  1  1  1  
short  2  2  2  2  
int 
2 
4 
4 
4 

long  4  4  4  4  
long long  8  8  8  8  
floating point numbers:  
float  4  4  4  4  
double  4  8  8  8  
special:  
size_t 
2 
4 
4 
4 
Our data is normally stored in variables, because we mostly want to work with the data, and so change this data. In our microcontroller all bytes in the random access memory (RAM) are variables (Arduino Uno 2 kibibyte).
The readonly memory (ROM
, flash
) is much bigger (Arduino Uno 32 kibibyte) than the SRAM
. So if we use constants, it is a good practice to add the const
keyword to our data type. The compiler will store this data in the flash, saving memory in the SRAM.
In Arduino (C) we must always assign a data type to a variable ore a constant. Her an example with a variable and a constant:
const float PI = 3.1415;
float circ, r;
void setup() {
Serial.begin(115200);
r = 11;
circ = 2*PI*r;
Serial.println(circ);
}
void loop() {}
In the sketch above we use also the number 2
as a constant. We can use integer constants or floating point constants.
Without further indications the number is treated as an integer. To make it a floating point constant we have to add a decimal point:
circ = 2.0*PI*r;
If we want to specify an integer constant with another data type we add a u
or U
to force the constant into an unsigned data format, a l
or L
to force the constant into a long data format, or both for an unsigned long constant.
a = 33u+v;
b = 33L*w;
c = 100000000UL/d;
If we want to add values in other bases than the base 10, we use the special formatters 0b
for binary and 0x
for hexadecimal (B like mentionned in the Arduino reference is not convenient, because of its limitation to one byte, we use the C++ variant "0b" instead):
int bina = 0b11011101;
uint16_t hexa = 0xFAC3;
If we need to convert one data type to another (also known as cast) we get 6 different functions to do this:
byte()
,char()
,float()
,int()
,long()
and word()
.
Serial.print()
Often we use the serial terminal for debugging or for showing results, if no display is available. The function Serial.print()
has the ability to convert a number with a second parameter to another base. Try the following program:
int dec_number = 1458;
int neg_dec_number = 1458;
void setup() {
Serial.begin(115200);
delay(500);
Serial.println(dec_number,BIN);
Serial.println(neg_dec_number,BIN);
Serial.println(dec_number,HEX);
Serial.println(neg_dec_number,HEX);
}
void loop() {}
Write an Arduino function that converts a positive decimal number (unsigned int
) to binary. The output will be an array of characters, also known as a Cstring (the 32 characters are accessed with the indexes 031, the 33 character automatically given to terminate the string (nullterminated string). Use the modulo operator in your program. The following code is already given:
// iot_jdinc7_dec2bin_string.ino
long dec_number = 1459;
void setup() {
Serial.begin(115200);
delay(1500);
Serial.println(dec2bin_string(dec_number));
}
void loop() {}
char * dec2bin_string(int dec_number) {
static char bin_string[33] = {"00000000000000000000000000000000"};
byte remainder;
//
// here comes your code
//
return bin_string;
}
In the above "Just do it"exercise you had to calculate by hand. Write an Arduino program to verify the calculations with the output below in the serial terminal. Document the source code and the output of the program. The program should begin with the following lines :
// iot_jdinc8_calculations.ino
long add1 = 37;
long add2 = 425;
long sub1 = 4477;
long sub2 = 425;
long mul1 = 121;
long mul2 = 425;
long div1 = 4477;
long div2 = 37;
void setup() {
Serial.begin(115200);
delay(500);
Serial.println(" ADDITIONS ");
....
}
void loop() {}
 ADDITIONS 
37 + 425 = 462
25 + 1a9 = 1ce
100101 + 110101001 = 111001110
 SUBTRACTIONS 
4477  425 = 4052
117d  1a9 = fd4
1000101111101  110101001 = 111111010100
 MULTIPLICATIONS 
121 · 425 = 51425
79 · 1a9 = c8e1
1111001 · 110101001 = 1100100011100001
 DIVISIONS 
4477 / 37 = 121
117d / 25 = 79
1000101111101 / 100101 = 1111001
Tip:
You can use the String()
function to get less Serial.print()
commands. Example:
Serial.print(String(add1,HEX) + " + " + String(add2,HEX) + " = " + String(add1+add2,HEX));
The assignment of a unique sequence of bits to the representation of each one of a set of numbers, letters or instructions is called a binary code.
Even the assignment of bits to the binary numeral system is a code. A binarycode working with 8 bit can represent the numbers from 0255_{(10)}. It can also represent 256 different characters or 256 different machine code instructions.
An often needed code, that is close family with the binary numeral system, is the BCDcode. BCD stands for Binary Coded Decimals.
Decimal 
2^{3} 
2^{2} 
2^{1} 
2^{0} 

0 
0  0  0  0 
1 
0  0  0  1 
2 
0  0  1  0 
3 
0  0  1  1 
4 
0  1  0  0 
5 
0  1  0  1 
6 
0  1  1  0 
7 
0  1  1  1 
8 
1  0  0  0 
9 
1  0  0  1 
1  0  1  0  
1  0  1  1  
1  1  0  0  
1  1  0  1  
1  1  1  0  
1  1  1  1 
One decimal number is represented with a nibble (4 bit) also known as tetrade. From the 16 tetrades only 10 are used. The don't carestates (unused tetrades) are named pseudotetrad(e)s.
To show that working with codes is not always straightforward we will look at an addition in BCD.
For results less than 10_{(10)} we have no problems. If the result is bigger, we have to deal with carry or a pseudotertade. In both cases a correction with the number 6_{(10)} is needed.
Examples
0001 1000_{(BCD)} 
+ 
control in decimal:  18_{(10)} 
+ 

0101 0100_{(BCD)} 
54_{(10)} 

1 
C 
1 
C 

  
 

0110 1100_{} 
+ 
72_{(10)} 

0110 
correction! (pseudotetrade)  
1 1 
C 

  

0111 0010_{(BCD)} 
0001 1000_{(BCD)} 
+ 
control in decimal:  18_{(10)} 
+ 

0011 1000_{(BCD)} 
38_{(10)} 

111 
C 
1 
C 

  
 

0101 0000_{} 
+ 
56_{(10)} 

0110 
correction! (carry)  

C 

  

0101 0110_{(BCD)} 
Other TetradeCodes are e.g. the Graycode, the Excess3code or the Aikencode.
A very important (and old :)) code, because it is used to represent text in computers, telecommunications equipment, and other devices is the American Standard Code for Information Interchange ASCII
.
Most modern characterencoding codes support many additional characters, but they all support the basic set of 128 ASCII characters (7 Bit).
Interesting are the first 32 codes. These were used as control characters to control devices (such as teletype machines or printers).
Two important control characters used even today are:
line feed
(10_{(10)}, 0x0A, LF, '\n'
) andcarriage return
(13_{(10)}, 0x0D, CR, '\r'
).In the physical media of typewriters and printers, line feed meant "down" and carriage return "across". (two axes of motion).
Line feed, also named newline
is used to signify the end of a line of text and the start of a new one (↵ Enter key on the keyboard). Carriage return can mean the same in software, so newline in character encoding can be defined as LF or CR or LF and CR combined into one (commonly called CR+LF or CRLF).
Linux and Unixlike systems use LF
('\n'
) as newline.
Windows and OS/2 (Apple) use CRLF
('\r\n'
).
Older systems like Apple II or Commodore C64 used CR
('\r'
) only.
Dec.  Oct.  Hex  Binary  Value  Dec.  Oct.  Hex  Binary  Value  

000  000  000  00000000  NUL  (Null char.)  064  100  040  01000000  @  
001  001  001  00000001  SOH  (Start of Header)  065  101  041  01000001  A  
002  002  002  00000010  STX  (Start of Text)  066  102  042  01000010  B  
003  003  003  00000011  ETX  (End of Text)  067  103  043  01000011  C  
004  004  004  00000100  EOT  (End of Transmission)  068  104  044  01000100  D  
005  005  005  00000101  ENQ  (Enquiry)  069  105  045  01000101  E  
006  006  006  00000110  ACK  (Acknowledgment)  070  106  046  01000110  F  
007  007  007  00000111  BEL  (Bell)  071  107  047  01000111  G  
008  010  008  00001000  BS  (Backspace)  072  110  048  01001000  H  
009  011  009  00001001  HT  (Horizontal Tab)  073  111  049  01001001  I  
010  012  00A  00001010  LF  (Line Feed)  074  112  04A  01001010  J  
011  013  00B  00001011  VT  (Vertical Tab)  075  113  04B  01001011  K  
012  014  00C  00001100  FF  (Form Feed)  076  114  04C  01001100  L  
013  015  00D  00001101  CR  (Carriage Return)  077  115  04D  01001101  M  
014  016  00E  00001110  SO  (Shift Out)  078  116  04E  01001110  N  
015  017  00F  00001111  SI  (Shift In)  079  117  04F  01001111  O  
016  020  010  00010000  DLE  (Data Link Escape)  080  120  050  01010000  P  
017  021  011  00010001  DC1  (XON) (Device Control 1)  081  121  051  01010001  Q  
018  022  012  00010010  DC2  (Device Control 2)  082  122  052  01010010  R  
019  023  013  00010011  DC3  (XOFF)(Device Control 3)  083  123  053  01010011  S  
020  024  014  00010100  DC4  (Device Control 4)  084  124  054  01010100  T  
021  025  015  00010101  NAK  (Negative Acknowledgement)  085  125  055  01010101  U  
022  026  016  00010110  SYN  (Synchronous Idle)  086  126  056  01010110  V  
023  027  017  00010111  ETB  (End of Trans. Block)  087  127  057  01010111  W  
024  030  018  00011000  CAN  (Cancel)  088  130  058  01011000  X  
025  031  019  00011001  EM  (End of Medium)  089  131  059  01011001  Y  
026  032  01A  00011010  SUB  (Substitute)  090  132  05A  01011010  Z  
027  033  01B  00011011  ESC  (Escape)  091  133  05B  01011011  [  
028  034  01C  00011100  FS  (File Separator)  092  134  05C  01011100  \  
029  035  01D  00011101  GS  (Group Separator)  093  135  05D  01011101  ]  
030  036  01E  00011110  RS  (Req to Send)(Rec Sep)  094  136  05E  01011110  ^  
031  037  01F  00011111  US  (Unit Separator)  095  137  05F  01011111  _  
032  040  020  00100000  SP  (Space)  096  140  060  01100000  `  
033  041  021  00100001  !  097  141  061  01100001  a  
034  042  022  00100010  "  098  142  062  01100010  b  
035  043  023  00100011  #  099  143  063  01100011  c  
036  044  024  00100100  $  100  144  064  01100100  d  
037  045  025  00100101  %  101  145  065  01100101  e  
038  046  026  00100110  &  102  146  066  01100110  f  
039  047  027  00100111  '  103  147  067  01100111  g  
040  050  028  00101000  (  104  150  068  01101000  h  
041  051  029  00101001  )  105  151  069  01101001  i  
042  052  02A  00101010  *  106  152  06A  01101010  j  
043  053  02B  00101011  +  107  153  06B  01101011  k  
044  054  02C  00101100  ,  108  154  06C  01101100  l  
045  055  02D  00101101    109  155  06D  01101101  m  
046  056  02E  00101110  .  110  156  06E  01101110  n  
047  057  02F  00101111  /  111  157  06F  01101111  o  
048  060  030  00110000  0  112  160  070  01110000  p  
049  061  031  00110001  1  113  161  071  01110001  q  
050  062  032  00110010  2  114  162  072  01110010  r  
051  063  033  00110011  3  115  163  073  01110011  s  
052  064  034  00110100  4  116  164  074  01110100  t  
053  065  035  00110101  5  117  165  075  01110101  u  
054  066  036  00110110  6  118  166  076  01110110  v  
055  067  037  00110111  7  119  167  077  01110111  w  
056  070  038  00111000  8  120  170  078  01111000  x  
057  071  039  00111001  9  121  171  079  01111001  y  
058  072  03A  00111010  :  122  172  07A  01111010  z  
059  073  03B  00111011  ;  123  173  07B  01111011  {  
060  074  03C  00111100  <  124  174  07C  01111100  
061  075  03D  00111101  =  125  175  07D  01111101  }  
062  076  03E  00111110  >  126  176  07E  01111110  ~  
063  077  03F  00111111  ?  127  177  07F  01111111  Nul 
An escape sequence is a combination of characters which represents no text. They are supposed to be intercepted by the program and perform a special function. In the C resp. C++ (Arduino) languages, it's a series of 2 or more characters, starting with a backslash (\
). They can be used directly in a string. Important escape characters:
Special character  Escape sequence 

line feed  \n 
carriage return  \r 
tabulator  \t 
Example:
Serial.print("Hello\tWorld\n");
Even with always better and cheaper dot matrix displays, sevensegment displays (SSD's) with LED's are a cheap and effective possibility for displaying decimal numerals even in the dark.
A single byte can encode the full state of a 7segmentdisplay including a dot (8 LED's).
We want to use a 7segment display (5161AS), to display the value of a nibble (4 bit) in hexadecimal. Search the data sheet and draw a circuit to connect the display to the Arduino Uno or Teensy 2.0. We will use one full Arduino Port (portB for Teensy 2.0 or portD for Arduino Uno), so connect segment A to pin 0 of portB (PB0, 2^{0}, LSB), segment B to Pin 1 of portB (PB1, 2^{1}) etc.. Don't forget the series resistors. Document the circuit and note your calculations for the resistors.
Test with an Arduino program that lights one LED after another with a delay of 1 second. Document the program.
Next we will light all LED's at once.
Tip: To address all Pins of portB we can write PORTB = 0xFF;
to light all LED's. With the command DDRB = 0xFF;
we can replace all 8 pinMode()
commands). Look for more info here: https://www.instructables.com/ArduinoandPortManipulation/.
To use our display we will use a decoding lookup table. The value we want to represent gives us the index of the table where we find the right byte to send to our SSD. Complete the following lookup table:
Bits of PortB:  7  6  5  4  3  2  1  0  

Segment:  dp 
g 
f 
e 
d 
c 
b 
a 
Byte: 

0  0  1  1  1  1  1  1  0x3F 

0  0  0  0  0  1  1  0  0x06 

0  
0  
0  
0  
0  
0  
0  
0  
0  
0  
0  
0  
0  
0 
Let's build a voltmeter with a 4 digit SSD showing the voltage from from 0 to 5000 mV. We will use a 4 digit SSD with time multiplex and the analog input A0 (0 V5 V) of a Teensy 2.0 or Arduino Uno.
As seen in electronics fundamentals because of the persistence of vision our eye doesn't see LED's flicker if the frequency is above 50 Hz. This is used for LED POV displays. We will switch the four digits on and off for 1 ms one after the other. This permits to use only 12 pins instead of 33. Look at the data sheet of our 4digit SSD SH5461AS, calculate the resistors and draw the circuit.
Connect the 7 segments to portB (PB0PB6) for Teensy 2.0 or portD for Arduino Uno. The 4 digits are switched with the lowest nibble of portD (Teensy, portB for Arduino Uno). Test the voltmeter program with a potentiometer (outer terminals to 5 V and ground, wiper (mostly the middle terminal) connected to the analog digital converter (ADC) pin A0 (ADC0)).