last updated: 01/04/20
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.
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 to 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. Digital signals have a finite resolution and can be processed and transmitted without introducing significant additional noise or distortion. Degradation can even be detected and corrected.
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!).
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 0
(zero), 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 number and numeral are used for the same. But we use always digit when talking about the single symbols that make up numerals.
Because we don't want to confound numbers of different numeral systems, it's a good idea to use subscribed parentheses with the base included: 1001_{(2)} ≠ 1001_{(10)}, 1001_{(2)} = 9_{(10)}
.
Base  number  

b  4  3  2  1  1  2  
Base  values  
b  b^{3}  b^{2}  b^{1}  b^{0}  b^{1}  b^{2} 
Computers and electronics have no fingers but only two ways 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 only 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), 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
(see PullUp in the tutorial ELEctronics FUndamentals).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. By grouping 4 positions in binary we get one position in hexadecimal.
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)}
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 see such a circuit in the next chapter.
Minuendsubtrahend = 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 
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 an digital adders it's by ways more convenient to use a method doing the subtraction by adding the b's (base) 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 afterwards. This gets very easy in a binary system because we get the b1complement (one's complement) by inverting the subtrahend. To get the two's complement we add the number one.
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 we the minuend can be lesser than 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 us a one bit multiplier circuit.
2^{0}
).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 / by 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 ts 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. The division also works 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
, 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 thausend (1000 decimal) and does not fit in the binary system. For 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 
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. If we per example one byte, the number 101_{(2)}
(5_{(10)}) will be written 00000101_{(2)}
.
Leading zeros are not required but they do help by showing the the bitlength of the number.
Negative numbers are created with the two’s complement (see later). The highest bit ("signbit"), is needed to flag the number as a negative number. To get the 2’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.
bytes  range unsigned  range signed 

1 
0 to 255 (2^{8}1) 
128 to 127 
2 
0 to 65535 (2^{16}1) 
32768 to 32767 
4 
0 to 4294967295 (2^{32}1) 
2147483648 to 2147483647 
Let's look at our Arduino platforms. Test the following program on an different Arduino platforms (e.g. Teensy 2.0 (Arduino Uno), Arduino Due, 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.print("bool: "); Serial.println(sizeof(bool));
Serial.print("byte: "); Serial.println(sizeof(byte));
Serial.print("uint16_t: "); Serial.println(sizeof(uint16_t));
Serial.print("word: "); Serial.println(sizeof(word));
Serial.print("unsigned int: "); Serial.println(sizeof(unsigned int));
Serial.print("unsigned long: "); Serial.println(sizeof(unsigned long));
Serial.print("unsigned long long: "); Serial.println(sizeof(unsigned long long));
Serial.println(" signed whole numbers ");
Serial.print("char: "); Serial.println(sizeof(char));
Serial.print("short: "); Serial.println(sizeof(short));
Serial.print("int: "); Serial.println(sizeof(int));
Serial.print("long: "); Serial.println(sizeof(long));
Serial.print("long long: "); Serial.println(sizeof(long long));
Serial.println(" floating point numbers ");
Serial.print("float: "); Serial.println(sizeof(float));
Serial.print("double: "); Serial.println(sizeof(double));
Serial.println(" special ");
Serial.print("size_t: "); Serial.println(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 our 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 course :).
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 and open the 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.print("bool:\t\t\t"); Serial.println(b);
Serial.print("unsigned int:\t\t"); Serial.println(ui);
Serial.print("unsigned int (bin):\t");Serial.println(ui,BIN);
Serial.print("unsigned int (oct):\t");Serial.println(ui,OCT);
Serial.print("unsigned int (hex):\t");Serial.println(ui,HEX);
Serial.print("int:\t\t\t");Serial.println(i);
Serial.print("float:\t\t\t");Serial.println(f);
Serial.print("char low byte:\t");Serial.println(sl);
Serial.print("char high byte:\t");Serial.println(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 one 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 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 right data type! A wrong data type can give erratic results. It's very difficult to find these errors, because they can show up only at certain numbers. 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 datatype unsigned where suited. To hold e.g. a digital pin number in Arduino (054) an int (32768 to 32767) is overkill. A byte is largely enough (0255).
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). 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). 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). 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 to 32767. On other boards (Teensy 3.1, ESP8266, ESP32) we have a 32bit (4byte) value for int
(2147483648 to 2147483647). 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 to 127) mostly used to store characters (see later).
long
is used for bigger integers (4 byte, 2147483648 to 2147483647). 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} to 3.4028235·10^{38}, 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.
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
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 Strings() 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).
Our readonly memory (ROM, flash) is much bigger (Arduino Uno 32 kibibyte). 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 our sketch 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 in the 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:
unsigned long dec_number = 1458;
char bin_string[33] = {"00000000000000000000000000000000"};
void setup() {
Serial.begin(115200);
delay(500);
dec2bin_string(dec_number,bin_string);
Serial.println(bin_string);
}
void loop() {}
void dec2bin_string(int dec_number, char bin_string[33]) {
byte remainder;
// here comes your code
}
 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
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 binary numbers from 0255. 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 here as example the 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 support the basic set of 128 ASCII characters (7 Bit).
Interesting are the first 32 codes. These were used for 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'
),and carriage 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). But 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 as newline. Windows and OS/2 (Apple) use CRLF (older systems like Apple II or Commodore C64 used CR 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. Note your calculations.
Test with an Arduino program that lights one LED after the other. 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 (DDRB = 0xFF replaces all 8 pinMode() commands). Look at the Arduino reference: https://www.arduino.cc/en/Reference/PortManipulation.
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  
0  
0  
0  
0  
0  
0  
0  
0  
0  
0 
Let's build a voltmeter with an 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 2 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)).