100% found this document useful (1 vote)
683 views

Embedded C Absolute Beginner-Compressed PDF

Uploaded by

akabhinav32
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
683 views

Embedded C Absolute Beginner-Compressed PDF

Uploaded by

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

Resume of theoretical and practical

knoweledges based on the


online video course
"Embedded C - absolute beginner"

MASSIMO ROSALIA
MASTER DEGREE ELECTRONIC ENGINEER
Contents
Introduction 3

Section 3 : Your first C program 3


Printf command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

Section 4 : Data types and variables 4


Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Sizeof command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Inizialization, difference between variable definition and declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Local and global variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

Section 5 - Variable address 8

Section 6 - Storage Class in C 9


Static variable between different functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Static storage class variable between different files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Static storage class function between different files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Extern storage class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
ASCII codes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

Section 7 - Functions in C 11
Return type of a function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Typecasting in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

Section 8 : Your first embedded C program 14


Embedded C Hello World for processor lower than M3 cortex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
Embedded C size of program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

Section 9 : Building process steps 19

Section 10 : Analyzing Embedded C code 20


Code memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Usage of memory browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
How to disassemble the code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Section 11 : Data types to manipulate floating point data 24

Section 12 : Taking input from the user adopting scanf command 26

Section 13 : Pointers in C language 28


Read and write operation on pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

Section 14 : The importance of the library < stint.h > 31

Section 15 : Operators in C language 32


Arithmetic and unary operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Relational operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Logical operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

1
Section 16 : Decisions making C language 36
The if statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
The if-else statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
The if-else-if ladder statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
The if-else-if ladder statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
Switch/case statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

Section 17 : Bitwise operators in C 39


Testing, clearing and setting bits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Bitwise shift operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

Section 18 : Embedded C coding for LED 43


How to enable a peripheral clock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Improve the LED turn on code exploiting bitwise operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

Section 20 : Looping in C 47
While loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Do-While loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
For loop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

Section 21 : Type qualifier const 50


Const pointer and different case study . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

Section 23 : Optimization 53

Section 24 : Type qualifier volatile 54


Usage of const and volatile together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

Section 25 : Structures and bit-fields 57


Aligned and un-aligned data access on structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Typedef command . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Structure pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Bitfield in structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63

Section 26 : Unions 64
Applicability of unions in Embedded system code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

Section 27 : Usage of bit-fields in embedded code 66

Section 28 : Keypad interfacing 68

Section 29 : Array in C 71
Read and write operation on a Array in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

Section 30 : Strings 73

Section 31 : Pre-processor directives in C 75


Macros in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Conditional compilation directives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77

2
Introduction
All the knoweledges in this documents were taken from the video online course Udemy Microcontroller Embedded C programming :
absolute beginner written by the Fastbit Embedded Brain Academy staff.
Hello everyone, I started to write this document because I feel I needed to have something written in order to search the argument and
repeat them whenever I forgot some details. Writing the document helped me for sure to better understand the arguments of the course,
when I was writing I had to explain to myself the topics in many details. This document is not thought to replace the video-course, not
at all. I studied the whole course and it’s a very complete and clear course.
This document purpose is to have something written to repeat the argument and easily search the topics by name searching by keyword
or looking at the table of contents in the beggining. I spent a lot of hourse trying to write it at the best of my chances. I hope that not
so many type or concept mistakes are present. I am a beginner in programming embedded C and this increases the probabilities I could
make some mistake or define something in not fully proper way. I hope to give a contribute to the students are studying this course now.
I hope this document could be useful tool to acquire still more effectively and deeply the topics of the course.
I want to give many thanks to the staff of the course which organized a very clear course. I say them thanks because I understood I am
very interested in embedded C programming in general due to this course. I will for sure continue furtherly to study embedded systems
hoping to find a job relative to them in next future. I don’t want anyone too bored with too many words. I always thought that facts
demonstrate a lot more than words. I wish you a interesting studying and I hope you will enjoy this course as I did.

Section 3 : Your first C program


The C program are always saved in file named with c extension (for example "main.c"). A C program is, at least, formed from one
function called Main Function. The main function will look like in Figure 1 . Main function is defined as an int function as standard, the
function types are going to be discuss further.

Figure 1

The function # include <> is needed to include a library.


The function printf prints on screen something, the argument of the text has to be included between "" always. The process of compilation
produces several files due to the inclusion of the library in the software:

1. main.c → main.i as the result of pre-compilation process


2. main.i → main.s which converts the code in Assembly code
3. main.s → main.o which converts Assembly code in Machine code

The compilation, then, converts an high level programming (visible to the human utent) to very low level language code, called
machine language, (used from machine to perform the single operations) through several steps.

Printf command
In order to understand the way to use in the better way the Printf command let’s refer to a very simple example shown in Figure 2.

Figure 2

Let’s suppose that it is wanted to print,as output, some messages using two different rows. Trying to run the code this is not going to
happen. Using more commands printf in this way a line on a single code is going to be obtained.
In order to dispose the text on a new line, an escape character has to be adopted\n which push next message to new line in output. The
code has to be modified as shown in Figure 3a. The output then will look as in Figure 3b. Adding the symbol " \" the escape sequence
is activated.

3
(a) (b)

Figure 3

There are many other escape sequences shown in Figure 4a.

(a) (b)

Figure 4

The commands’ description relative to \n and \r sound very similar. It is worth then to clarify the main difference between the two
escape characters. The escape character \n moves the cursor to the beginning of the new line vertically. The escape character \r moves
the cursor to the beginning of the current line horizontally. Figure 4b shows an example of the movement of the cursor using the code
reported in the figure. The action of the first printf is reported in the top part of the figure while the bottom part shows the effect of the
movement due to the second printf applied. One of the command to remark regards for sure how to insert comments in C programming
language. Comments are used to provide the description or documentation aboute the written code. Comments are very important to
help developers to understand the code after long time the code was written. The comments are ignored from the compiler, they do not
affect program instructions. The comments in C language can be single line comments, inserting the commment text after the character
" //" in the considered line. The other type is multiple line comments, using " /* " as beginning of comments and " * /" to finish the
comments section

Section 4 : Data types and variables


Data type is used for declaring the type of a variable. In C programming, Data type determine the type and size of data associated with
variables. The programmer should decide the type of a variable before storing any value to it. In order to represent an integer number
integer data types are going to be use in C language. The real numbers are represented adopting float data types instead. The following
analysis is focused on integer data types. The type of data could be signed or unsigned. The signed data types are the one listed below:

• char • long int


• short int
• int • long long int

The unsigned data types can be create adding the key word unsigned before the signed type ones (for example unsigned char or
unsigned short int ). Every data type identifies a precise size in memory storage and a precise range related to the representable values.
The size of the memory storage for each type depends on the compiler used, the sizes are not fixed in the same way for each compiler
then. These data for the compiler used in the gcc compiler are shown in Figure 5. These data are available in the reference value of the
compiler

4
Figure 5

It is worth to remark that C standard doesn’t fix storage sizes of different data types. It only specifies about the minimum and
maximum value representable.
These following data types sizes are fixed independently of compilers in C language :
• char (signed or unsigned) → 1 bytes
• short (signed or unsigned) → 2 bytes
• long long (signed or unsigned) → 8 bytes

Char data type allows to store a single character (ASCII standard).

Variables
Variable definition always includes "data type" followed by "variable name". Before using a variable, it’s necessary define its data type
according to the program logic and needs. A variable definition is shown in Figure 6.

Figure 6

The variables are identifier for your data. Data are usually stored in computer memory. A variable acts as a label to a memory
location where data is stored. Variable names are not stored inside the computer memory, the compiler replaces them with memory
location addresses during data manipulation. The memory organization in general is formed as in Figure 7.

Figure 7

In this case each register counts 8 bits, this can change due to the architecture of the memory. Each register is identified by a number
(for example 0x0800). Each register can store a value of 8 bits then in this case. The name assigned to a variable is not stored in
the compiler, the compiler just assignes such value to the memory location number. The name of variable is just used to simplify the
operation for the programmer.
The rules in naming variables are listed below.

5
• the variable name has not to be longer than 30 characters
• a variable name can only alphabets (both uppercase and lowercase and underscore)
• the first letter of the variable cannote be a number, it should be an alphabet or underscore
• it’s not possible to use keywords for C commands as part of variable names

In order to use the command printf to print variables’ value it’s necessary to define the output data type.
Format specifier is needed for that. The types of format specifiers are shown in Figure 8a. The example in Figure 8b shows how to find
out the size of the variable printing it in the output using a integer format specifier. The format specifier defines the position where the
variable value will be printed (in the example of Figure 8b format specifier is placed at the end of the line).

(a) (b)

Figure 8

Sizeof command
Sizeof command is used to find out the size of a variable. The output of this operator may be different on different machines due to
different compilers adopted. To clarify the concept it’s possible to observe Figure 9.

Figure 9

The sizeof command accepts a variable or data type name as argument. The char data type is inserted in Figure 9. The output of the
command will show the size of the variable or data type in bytes. The size of the data for our compiler is 1 byte for char data type.

Inizialization, difference between variable definition and declaration


Before to use a variable you have to define it. Variable defition (unproperly also called variable declaration) informs the compiler on
how big has the be the memory space reserved for such variable. If the data type is char the compiler reserve 1 byte, if the data type is int
4 bytes are going to be reserved (referred to the considered compiler). The variable initialization implies to assign the initial value to a
variable. The initialization has to be applied after the definition of the variable. Figure 10a shows the correct order of the two operations.
Figure 10b is an illegal operation, it’s not possible to assign a value to a variable before its definition. The compiler generates an error
for a code as the one indicated in Figure 10b. Definition and Initialization of a variable can be applied in one single line code. This is
correct from the point of view of the compiler. An example is shown in Figure 10c.

6
(a) (b)

(c)

Figure 10

It is worth to remark in the end the difference between variable definition and declaration.
• a variable is defined when the compiler generates instructions to allocate the storage of the variable
• a variable is declared when the compiler is informed that a variable exists along with its type. The compiler does not generate
instructions to allocate generate instructions to allocate the storage for the variable a that point.
• A variable definitino is also a declaration, but not all variable declarations are definitions.
An example can clarifies a bit the idea of difference more practically(Figure 11a-b). Figure 11a shows a normal definition, adding the
keyword extern this becomes a declaration. The variable was already defined somewhere else and it’s just recalled here.

(a) (b)

Figure 11

Local and global variables


Another concept to remark regards the difference between the local and the global variables. The example in Figure 12 regards a local
variable referred to a function.

(a) (b)

Figure 12

The variable myScore is local to the function main (functions will be discussed more in details in the continuing of the course). When
the function myFun1 is recalled the compiler try to assign the value 800 to the variable myScore due to the instructions of the function
myFun1. The compiler generates an error because the variable myScore is local to the function main. This means that the other functions
don’t see that variable, if not specified, and cannot change its value. Local function loses its existence out of the function. Observing
Figure 12b, the variable myScore was moved out of the function main and it’s now a global variable. The compiler produces in this case
the desired output.
To summarize, global variables are visible to all the functions of a program. They’re everywhere, it’s possible to access them even from
another file of the project. All uninitialized global variables will have 0 as default value. Their lifetime will last until the end of the
exection of the program.
Local variable lose existence once the execution control comes out of the function body. The default value is unpredictable (random
value). Their lifetime last until the end of a function in which the variable is defined.

7
Section 5 - Variable address
As it’s possible to observe in Figure 13 each register is identified by a address number. It could be useful to know how to find out the
address relative to the register where the variable is stored.

Figure 13

It is worth to underline than when it is used a character between ” as in Figure 13 this indicates the address memory number where
that ASCII character is stored. The format specifier % p allows to print the address number relative to variable in exadecimal format. To
indicate, finally, the address it is needed to write a & before the name of variable. The compiler print then correctly the address number
as shown in the bottom part of Figure 13. The notation & - name variable indicates the address associate to a variable in general then.
The memory address is also called a pointer in C because it "points" to the data in the register.
It’s possible to assign the pointer number to a variable. It a normal assigned is applied the compiler generates a warning (Figure 14).

Figure 14

This warning is produced because we’re trying to assign a number to a pointer. A pointer is another data type. The assignment can’t
be applied in this way. To assign a variable to a pointer it’s necessary an operation called casting that converts a pointer in a number.
A casting can be applied as in the first row in Figure 15. The top part shows the instruction code while the bottom one the result of the
compiler. In Figure 15a the compiler produced the desired output. The programmer could obtain some warnings like the one in Figure
15b. This indicates a mismatch between the size of variable and the size of the casting it is going to be applied. The same code working
on a machine could not work on a different one because the new compiler could present a different standard size for the same data type.
For example the standard long could store 8 bytes for one compiler and 4 bytes for another one. It’s possible to find out the datatype
sizes using the command sizeof explained in paragraph 2.2. The format specifier % ldX indicates a long signed data type in exadecimal
notation. A complete list of format specifier in C language is shown in Figure 16.

(a)

(b)

Figure 15

Figure 16

8
Section 6 - Storage Class in C
The storage classes in C determine the

• Scope of a variable • Life time of a variable


• Visibility of a variable or function

Scope,visibility and lifetime are features of a variable- These features can be ,modified by using storage class specifiers.
The widely used storage class specifiers in C are:

• Static • Extern

Figure 17

Let’s observe the example of Figure 17. The idea is to write a program that counts how many times the main function recalls a
subfunction. The output of the compiler is shown on the bottom right corner of Figure 17. As it’s evident, the program doesn’t work
as it should do. The variable count has been defined as local in the function myFun1. At the line 10, compiler recall function myFun1,
increments and prints correctly the result. The main problem is that when the execution goes out of the subfunction myFun1 the variable
count lose its existance because it’s local to the subfunction. At the execution at line 11 compiler initializes again the count=0 and
increments showing wrong result. One of the possible solution is to make global the variable count moving its definition between main
function as it happen in Figure 18. It’s possible to notice that program counts in proper way now.

Figure 18

A standard global variable was created then. This could be modified from any other function from this or external file. It’s not
advisable an approach like this.

Static variable between different functions


The requirement for this program is to have a global variable private to a function. In other words, the target is to get a private variable
which doesn’t lose its existence even ig the execution control goes out of the scope of that variable.
This can be achieved using the keyword static before the standard definition of variable. This is applied in Figure 19. The definition was
moved again inside the subfunction adding the keyword static. The program works as well. count is now a global variable private to the
function myFun1.
The scope of the function is now global but visibility is limited inside the function.

Figure 19

9
Static storage class variable between different files
Let’s extend the concept of the static variable to multiple files. In the example of Figure 20 another file file1.c is added to the project.

Figure 20

Using the code in Figure 20 the code generates a warning and an error. To prevent the first warning a prototype of function has to be
added. In order to avoid the error the variable mainPrivateData has to be recalled from file file1.c where a value was tried to be assigned
to variable but variable is not here defined.
The two files have to be modified then as in Figure 21.

Figure 21

The prototype of the function defined in file1.c to recall this function from the file main.c. The recalling of the function in file1.c
from main.c was performed through extern keyword. As it’s seen the program works properly now.
The main problem here, similarly to the case of the function, is that the variable that was defined globally could be changed from other
files of the same project.
To make the variable mainPrivateData global just for the file it is necessary to replace int mainPrivateData; with static int mainPrivate-
Data; .

Static storage class function between different files


It’s possible to suppose a very similar case to the previous ones. It’s possible to have a function that manage a very delicate parameter
for the system. The programmer doesn’t want that everyone from each file could modify this variable. It is worth to observe Figure 22.

Figure 22

It’s possible to notice that the compiler is able to change the variable system_ clock. To keep the function visible just from the file
main.c it’s possible to adopt the usual solution and add the keyword static before the definition of the function.

Extern storage class


Extern storage class specifier is used to access the global variable which is defined outside the scope of a file. Extern storage class
specifier can be also used during the function call, when the function is defined outside the scope of the file.
The keyword extern is relevant only when your project is made of multiple files.
The keyword extern is used to extend the visibility of a function or variable.

10
ASCII codes
It is worth to see how it’s possible to store a character. It’s necessary to assign a character to a variable to try to print it.

Figure 23

In Figure 23 the characters were assigned to variables inserting them between ” symbol. In order to print a character using printf
command it is necessary to adopt the format specifier %c. The variables assigned to the ASCII characters are inserted as arguments of
the printf command in the usual way. It’s possible to store characters using this approach.

Section 7 - Functions in C
The executable instructions in C are always written inside a function. It’s possible to define a function as the collection of statements to
perform a specific task. Every program in C is made, at least, of one function called main. Using functions allows to bring modularity to
your code, easyness in debugging and modifying it. This functions approach helps to minimize code size and reducing redundancy.
The most interesting pratical purpose of the function is to program a function for a task it is needed to be performed again and again.
Once created, it is possible to call that function everytime it is needed to perform that task just recalling it and assigning the proper input
values. This obviously reduces final executable size of the program and redundancy.
The definition of a function is accomplished with the following set of instructions (Figure 24).

Figure 24

It is needed to define a function name, the rules to name the function are the same ones already discussed regarding the variables .The
return_ data _ type which defines the output datatype that function produces. The parameter list defines the input values to function, it
is placed always between round brackets. The body of the function, in the end, represents the set of executable instructions performed
from the function itself, the body of the function is always written between curly brackets.
The definition of the function without the body is called function prototype. There are two ways to define the main function as it’s shown
in Figure 25. The no input argument definition is always used in programming embedded systems.

Figure 25

It has to be remarked that main function has to be defined as int according to the C standard C89 and above.
Main function takes only zero or two arguments. Main() is a special function where the execution of a program starts and ends. Main()
returns the status of a program to the Parent process, which could be the operative system in the case of PC, (0 means success, non zero

11
means error).
It is worth to see how to recall another function from main (Figure 26).

(a) (b)

(c)

Figure 26

In the case of Figure 26a the compiler is going to produce a warning indicated in Figure 26c. This warning is indicated that, as in the
case of the variables, it’s necessary to define that function before to use it. This is applied inserting the prototype of the function (Figure
26b).

Return type of a function


In the previous case, a void function has been defined. It is worth to remember that void function don’t return any data value. It is
possible to modify the code to get same output changing the function type (Figure 27).

(a) (b)

Figure 27

The function add _ numbers has been modified to return an int datatype. The return value sum is now available to main. The variable
retValue is defined equal to sum at line 8 of Figure 27a. The compiler doesn’t produces any warning or errors and the output is correct
(Figure 27b).
It’s possible to create a header file creating from the path File → New → Header File in STM32 IDE.
This file will be named as name.h and prototypes of the functions can be inserted inside. It’s not necessary then to include the prototypes
of the functions in main.c file.

12
Typecasting in C
Typecasting is a way of converting a variable or data from one data type to another data type. Data will be truncated when the higher
data type is converted in to lower one. There are two types of casting.

• Implicit casting (performed by Compiler) • Explicit casting (performed by Programmmer)

In order to clarify the difference let’s introduce some examples.

(a) (b) (c)

(d)

Figure 28

In the example of Figure 28b an unsigned char variable is defined (size = 1 byte), it is equal to the sum of two constant.
If not specified, the compiler will consider the constant as 4 byte data. The sum of these two 4 byte data will reach a value not
representable in the unsigned char variable. This will cause the compiler warning in Figure 28a. In the case of Figure 28b there is
basically the same error from programmer, the difference is that the sum value can be represented in a 1byte variable. The compiler
won’t generate the warning because there won’t be data loss.
This is an example of implicit casting, the compiler didn’t receive any instruction about the data type and it fixed a 4 bytes data for the
constant as default. The explicit casting is available in Figure 28c where the programmer has specified that the result of the sum has to
be an unsigned char. The compiler won’t produce any warning in this case.
Another example is shown in Figure 29. In Figure 29a a float variable is defined. The division present two integers, the compiler sees
an integer divided by another integer and the result will be a integer as well in this case. The output shown in Figure 29c is an integer,
the number after the point were lost (information loss). In Figure 28b the programmer specified float type for the constant 80 (explicit
casting), the compiler will understand then that the other constant 3 has to be converted as a float constant as well. It’s possible to see in
the output obtained in this way (Figure 29d) that there is no information loss here.

(a) (b) (c) (d)

Figure 29

13
Section 8 : Your first embedded C program
To create a new embedded (Cprogram for target board) it’s necessary follow this path File → New → STM32 Project. It is needed to
select the board and assign a name to the project. The selected settings are shown in Figure 30.

Figure 30

Once created the project the start-up file is very important and it will be discussed further in the course. In a new project it’s always
present a file main.c in src folder where it’s possible to write our code. The purpose of the first program will be to print Hello World as
well as it was done the first time in C online compiler. The problem is that not any monitor is connected to our embedded board. The
solution is available due to the properties of the processor ARM Cortex M4 present on Nucleo Board.
The following discussion is valid just from processor ARM ortex M3 or higher. The printf function exploits the SWO pin which stands
for Serial Wire Output of SWD interface.

Figure 31

Figure 31 shows the ARM processor on the board and another circuitry which is the ST link debug circuitry which allows to commu-
nicate between PC and ARM cortex processor using USB connection. The SWO pin connect the two circuitres.
It is worth to focus on ARM Cortex M4 processor. Observing Figure 32, the ITM allows to use the printf function and many other
purposes available in the figure decription.
.The Debug connector presents 2 pins dedicated to debug (it’s possible to read the content of memory location in the processor for
example) and another one dedicated to ITM data transmission called SWO.

Figure 32

14
The serial wire debug, or SWD, is a two-wire protocol for accessing the ARM debug interface. It is part of the ARM debug interface
specification v5 and is an alternative to JTAG. The physical layer of SWD consists of two lines:

• SWDIO : a bi-directional data line


• a clock driven by the host

By using SWD interface it should be possible to prigram MCUs internal flash, you can access memory regions, add breakpoint,
stop/run CPU. The other good thing about SWD is you can use the serial wire viewer for your printf statement for debugging. Let’s
make a comparison between SWD and JTAG protocol communication. JTAG is adopted by the cortex M7/9 family but the M cortex
family introduced the serial wire debug SWD which allow to reduce the pins from 4 to 2. In addition SWD interface provides one more
pin called SWO (serial wire output) which is used for single wire viewing(SWV) which is a low cost tracing technology shown in Figure
33a.

(a) (b)

Figure 33

In Figure 33b there is a zoom on ITM unit. It is possible to see a FIFO memory. When printf is used the characters are written in
the FIFO memory. It is worth to underline that the ITM unit is connected to the SWO pin which transmits data out of the processor, the
printf data will pass through SWO pin then. This SWO pin is connected to ST link circuitry of the board and can be captured using our
debug software (IDE).
Let’s see how to implement our C program to print Hello World.

(a) (b)

Figure 34

The code in Figure 34a regards the classic printf as we already know it. To let it work it is needed to apply some modifies to the
code.

15
To allow this code to work it is needed to add in syscall file like the one in Figure 35.

Figure 35

This code has to be copied directly inside the code of the function syscall. This code works just for processor from ARM Cortex M3
to go on as it’s specified in the top bar of Figure 35.

(a) (b)

Figure 36

It’s needed now to find a function called write inside syscall file (Figure 36a). It is needed now to search another function called
ITM _ SendChar and recall this function inside write function. Last thing to do is replace the argument of ITM _ SendChar with the one
inside the write function (*ptr++) as it’s shown in Figure 36b.
It is worth to underline step by step what was done here.

Figure 37

As it’s shown in Figure 37, when printf function (standard library function) is called, the printf function calls a low lever library
function called write. In this case, the function ITM_SendCharis called because the data are passed inside the FIFO inside ITM and get
the trace in order to transmit it through SWO pin.
If the output device was different, as a LCD monitor, we could adopt another function called LCD_SendChar. If another protocol as
UART has to be used another function UART _SendChar is going to be called and so on.
In order to compile the usual command Buil Project is used. In this case a file called Name_Project.elf is going to be generated. This
file is adapted for the target machine (the ST development board for example). The operation to adapt the code from a type of core
architecture to another is called cross compilation. In this case the host compiler (gcc compiler) is used to produce executable files for
different machines (.elf / .bin / .hex). The debugging uses .elf files. The files .bin are used when some file have to be furnished to a
customer but it has not be possible for customer look at source code (copyright purpose).
A lot more common compilation is the native compilation. In this case the code is written on a machine and the executable file runs on
the same machine. An example is the .exe file generated by compiler on PC as minigw_64 or gcc compilers.
In order to transfer the executable file to our target machine a debugging operation is needed. It is necessary to apply a right click on the
project and select Debug as and finally Debug Configuration.

16
(a) (b) (c)

Figure 38

On the left bar in Figure 38a it’s possible to apply a double click on STM 32 debugging and a debugging configuration is going to be
generated. It’s possible to see here the name of the file.elf which is going to be loaded in our target machine, the name of the project as
wee. Switching to Debugger submenu in top bar (Figure 38b) . The Debug probe is going to be ST link-GDB server for our purpose
and the interface is going to be SWD. The serial wire viewer SWV has to be enabled (Figure 38c), the default settings about clock are
fine. Once all these modifies have been applied, it’s possible to click on Apply and Close.
The debugging process will be enabled applying a right click on the project, Debug as → STM32 MCU C/C++ application. If the
process is accomplished correctly it’s possible to swith in debugging mode (IDE is going to ask us to switch mode).
Another very important step is enable to look the output of the target machine through SWV.

(a) (b)

(c) (d)

Figure 39

Figure 39a shows how to enable the SWV, the output will be shown in the bottom bar of IDE beside Console. It is needed to click
here on Configure trace shown in Figure 39d. The bottom part of the windows that appears (Figure 39b) is used to enable 0 port to
configure SWV output. It is enough to click on ok to save the changements. It’s possible to reset the development board clicking on reset
chip (Figure 39c) and on the same bar click on run to get the output on the bottom window of SWV that was previously opened.

Embedded C Hello World for processor lower than M3 cortex


The ST-link used for debugging is not available on M0 or M0+ Cortex architecture. In order to print something it is necessary to apply
some changements to the previous project (Figure 40).

(a)

(b)

(c)

Figure 40

17
It’s possible to create a new project with the same main.c and syscall file as the previous project. Following the path (Right click on
project → Debug as → Debug cofigurations). On the window opened it’s possible to select on the left barSTM32 MCU debugging and
click on the left top corner New launch configuration. In this way it’s possible to create a new debugging configuration for our project.
Going inside the Debugger submenu it’s needed to change the debug probe to ST-link (Open OCD). Another modify has to be applied
in Startup submenu pasting the code in bottom part of Figure 40b (this operation regards the adding of semi-hosting run command in
Figure 40a). In order to apply the changements to linker argument (Figure 40a), it is needed to follow the path (Right click on project →
Properties → Expand C/C++ build→ Settings → Tool settings → Linker menu → Miscellaneous ).
Finally it is possible to click on button shown in Figure 40c and paste the code shown in Figure 40a and save.
The main.c, finally, has to be modified adding the code in Figure 40a (initialise function inside main function and adding the prototype
to the main function). The last thing to do is exclude the syscall file from project just apply a right click on the file and go to properties.
In C/C++ build enable the voice Exclude resource from the build. It’s possible finally to debug and pass to the switching code. The rest
of the settings in debug modality is the same as for Cortex M4 procedure or higher.

Embedded C size of program


The sizeof is going to perform the same task already performed by native compilation already analysed previously in the course. In this
case the main difference will be perform a cross compilation to produce an executable file for our target machine.
The syscall can be performed exactly in the same way as it was seen for first Embedded C program for M cortex equal or higher than
M3 family. The code already implemented could be just copied and pasted from the syscall of the previous project then. It is possible to
write in a code in main as it’s shown in Figure 41.

(a) (b)

Figure 41

It’s possible now to set debug configuration. As usual Right click on project → Debug as → Debug configuration. At this point it’s
enough to double click on the STM32 MCU debugging to create a new debugging configuration.
In the debugger submenu it’s possible to select as probe ST link - GDB server, interface SWD and enable SWV just keeping the default
settings. After all of this, just apply the settings and start debugging. It’s possible to enable the SWV output as it’s seen in the first
embedded C program and activate channel 0 in the Configure trace menu. It’s worth to mention (look Figure 39b) that the other channel
could be activated to see the output of another command (a different command than printf ). After reset the chip, it’s possible to use the
button step over (Figure 41b) instead than resume used previously. Resume execute all the instructions and generate all the output in
once, Step over shows the output of just one instruction and it’s possible to push it time after time to get the outputs relative to all the
instructions in main once per time. In this case using step over we’re going to obtain the output of single printf functions once per time.

18
Section 9 : Building process steps
The building process is made of these following steps:

• pre-processing • producing final executable file (for example elf )


• parsing
• producing object files • post-processing of final executable

The build process can be summarized in two main stages :

• compilation stage • linking stage

The compilation stage and its relative files generated are shown in Figure 42a.

(a) (b)

(c)

Figure 42

It’s possible to see, as it was previously mentioned, that compilation converts an higher level code (C code) in machine code after
several steps. The pre-processing stage produces .i files. In this stage all the # include files will be resolved and C macros as well. The
parsing step is going to control the syntax in .i file which total depends on the source code written by programmer, any warning or error
will be generated and communicated to programmer in this stage. The code generator converts the .i file in assembly code (.s file). It’s
possible to see in Figure 42b how just one instruction in C code is converted in many instruction in Assembly code. During last phase
the assembler converts the code in machine code ( .o file)
The linking stage shown in Figure 42c collects all the machine code instructions relative to one C code file and merge it (adding eventual
third part or external libraries instructions) in one executable file (elf).
The post-processing stage regards all the operations performed after the executable file creation. In Figure 42c, for example, a tool is
adopted to convert elf file to bin file. All these stages are automatically performed from our cross compiler. To look at these files it’s
necessary to look in the building folder (the folder in which the compiler store all the compilation files).
The check the location of this folder it’s possible to follow this path (Right click on project name → Properties →Resources). At this
point, it is necessary to click on the button shown in Figure 42d. Opening the project name folder and the subfolder Debug it’s not
possible to find all these files (.i ; .s ; .o). As standard settings the compiler generates these files and uses them internally but it doesn’t
store it. It is necessary to change some settings.
In order to do this (Right click on project name → Properties →C/C++ build menu →Settings → Tool Settings →MCU GCC Compiler
→Miscellaneous). Click on the top right corner on add icon and paste "-save-temps" in the empty bar. Finally click on apply and
apply and close. Rebuilding the process it’s now possible to see all the folders in the build process folder. It’s possible to find .i and
.s files for each source file. Opening the src subfolder it’s possible to find the machine code .o files as well. The machine code are not
understable by programmer being files adapted for machine. Summarizing all the compilation code can be resumed in three phases:
pre-processing,compilation and finally post-processing.

19
Section 10 : Analyzing Embedded C code
Let’s introduce the concept of microcontroller. A microcontroller (MCU, µC) is a small computer system on a single chip. Its resoures
and capabilities such as memory, speed, external interfaces are very much limited than of a desktop computer because of MCU targets
embedded applications.
A typical microcontroller includes a processor, volatile and non*volatile memories, input/output (I/O) pins, peripherals, clock, bus
interfaces on a single chip. A microcontroller, due to its properties, is also called System on Chip or SoC. The Figure 43 shows the
typical parts of a micro-processor, it’s possible to see the processor, several types of peripherals and memories and obviously an internal
clock and bus.

(a) (b)

Figure 43

In Figure 43b it’s possible to see a simple type or microprocessor internal architecture. Once the programmer write a program, the
program is stored inside program memory which is non-volatile. In program memory the address location number is stored for each
instruction. The addresses of each instruction can be shared with other parts of microprocessor through data bus.
The data memory, which is volatile, is used to store temporarly some data. Its usage is relative mainly to run-time of the program. It
is worth to remember that volatile memory lose the data stored when the power of the system is disconnected while the non-volatile
memory keep the data stored in this situation too. Data are shared using data bus.

(a) (b)

Figure 44

Figure 44a shows all the interal part of a STM 32 microcontroller. As it is known, the processor is produced by ARM Cortex (not
developed by ST). The ST customizes it. Apart the processor, all the other parts are implemented by ST. It’s possible to see several
connectivity interfaces such as SPI, USB,CAN and other standards. The system presents an internal ADC,DAC and a temperature sensor
(available in analog section). In control section, control related peripherals are shown (peripherals relative to the internal communi-
cations). The system section, on the other hand, shows all the system internal peripherals as well such as PLL or internal oscillator.
The dark blue sections, on the top of connectivity section, shows the type of memories implemented in the microcontroller. The code
memory (non-volatile) is the Flash memory, the volatile one is the S-RAM. It is present an external memory support too.
Figure 44b shows PIC16FF887 manufactured by Microchip technology. The processor is produced from the microcontroller. The in-
structions to program microprocessor will be different due to different manufacturers, the main parts are almost the same as shown in
Figure 44a-44b.

20
Code memory
The purpose of the code (program) is to store instructions and constant data of you program.
There are different types of code memory:
• ROM (Read only memory)
MPROM (Mask Programmable Read Only Memory)
EPROM (Ultraviolet Erasable Programmable ROM)
EEPROM (Electrically Erasable Programmable ROM)
• OTP (On time programmable)
• Flash

• FRAM (Ferroelectric Random Access memory)


In the list, in the ROM family, the MPROM can be programmed just once as in the case of OTP memories. The EPROM are
theoretically re-programmable but the process of data erasing is very long and obsolete nowadays (due to ultraviolet ray exposition).
The EEPROM and flash present a much more easier and faster method in erasing stored data. The FRAM are faster than flash but much
more expensive.

Usage of memory browser


Let’s observe the example of Figure 45. It is a simple program in which two global variables are defined and a constant value is assigned
to them. The value of the sum of the two constant is then assigned to another variable.

(a) (b)

Figure 45

In order to chech the memory addresses in which the value of the variable is going to be stored it is necessary compile (right click
on project name → Build Project). The .elf is going to be generated. Let’s debug (right click on project name → Debug as → STM32
MCU C/C++ application).
In the debug modality, let’s click on (Window → Show View ). At this point the voices Memory and Memory Browser are going to be
useful for our purposes. Clicking on Memory Browser a bar like the one in Figure 45b is going to appear. In order to get the addresses
number it is necessary to refer to the reference manual of our micro-controller.
The micro-controller family considereded here is the STM32F446 family.

(a) (b)

(c) (d)

Figure 46

21
The flash memory organization is available in the paragraph Embedded flash memory. The flash memory address location are the
ones referred as main memory (non-volatile memory). The voice system memory identifies a read-only memory (the programmer cannot
write in this memory then). The location of flash memory are very important for our purposes because these memory locations form
the program memory shown in Figure 43b. Using memory browser it is possible to observe the machine code relative to every memory
location address as shown in Figure 46b. In this case the informations are shown bit by bit then. Applying a right click on this window
and set the column to 1 it’s possible to see informations stored for groups of 4 bytes. Applying a right click and selecting cell size it’s
possible to see the information for group of 1 byte.
Let’s examine now the data memory stored in SRAM, to check in location it is necessary to return to browse the reference manual. It is
possible to find something similar to Figure 46c. In this case two memory data are present (SRAM1 and SRAM2).
The SRAM-1 location starts from the address 0x20000000 in this example. Inserting this address in Memory Browser and setting radix
to Decimal Signed. In Figure 46d it’s possible to see the variable constant of the program written in Figure 46a in the relative memory
locations.
The executable file .elf was downloaded in the flash memory of the microcontroller, the data memory then was saved in flash memory.
As previously mentioned, the data constant were found in SRAM of microcontroller. How could this happen? How the data were
trasferred from flash memory to SRAM? Let’s analyze the .elf file of our project to answer this question. In order to do that browse in
the project folder, open Debug folder and open with a text editor the file name_project.list. Opening this file and browsing until section
something similar to Figure 48 will be available

Figure 47

The section .text collects all the instructions of the written code. The section .rodata collects all the read-only data of the program.
The .data section shows all the data of the program (all the memorized constant are stored here). The address in which the constant
are stored is in the LMA column in the .text section. LMA stands for Load Memory Address, the memory location in which constant
are loaded then. The memory location in Figure 48 is equal to 0800138c or more precisely 0x0800138c. As it’s possible to see in
Figure 46b the flash memory location starts at the address 0x0800000. This means that this is a memory address of Flash Memory.
The LMA indicates where the section is currently loaded while VMA, or virtually memory address, indicates where the section should
finally copied to. Checking the VMA column address in .text section it is the SRAM address (0x20000000) as it’s shown in Figure 46d.
This means that the constant data were copied by the software from flash memory to SRAM memory. What is worth to notice is that
the program in Figure 45a is very simple. Clearly no one of the written instruction could imply this operation of data trasnferring. The
relative instructions are inserted in the start-up code present in the folder Start-up in the project.

Figure 48

The instruction Reset_Handler is a hardware instruction written in Assembly. It is possible to see some definition of the address
relative to source and destinations files and the data moving instructions which follow the voices CopyDataInit: and LoopCopyDataInt.

22
In order to control the effects of the instructions in the startup section it’s possible to apply some breakpoints in the code.

(a) (b)

Figure 49

A breakpoint look like that blue mark on the left in Figure 49a. To enable this mark it is enough to click twice on the interested line.
A break-point allows to stop the execution of our program at the desired point. Applying more breakpoints it’s possible to control the
code on the selected lines. In order to stop the execution at the first breakpoint it’s necessary to reset the chip in debug mode.
Focusing on the commands in Figure 48 placed after the voice CopyDataInt it’s easy to see operations of copying on registers. In order
to check the contents of the register let’s follow the path, in debug mode, Show View → Register. A window like the one shown in Figure
49b will appear. The value of each register is available on right part, clicking on the register on the bottom part the address number of
the register is shown (register r4 in Figure 49b for example).

How to disassemble the code


The verb to disassemble means, in programming, translate a program from machine code into a higher-level programming language. In
our case, it’s possible to perform this operation startin from the .elf file of the project. To enable this feature in the STM32 IDE, it’s
necessary to follow the path Window → Show View → Disassembly. The program will show us a window similar to the one shown in
Figure 50a. The first two column show the address memory location relative to the code operations in decimal and hexadecimal notation.

(a) (b) (c)

Figure 50

In Figure 50a, the light blue section shows instructions that are reading the contents from some registers. The instruction in red
execute the contents of two register and it saves the content in another one. Observing Figure 50b applying two breakpoints in the code
section the markers relative to breakpoints will be placed at the same points in the disassembly window. If the programmer is more
interested in two consecutive instructions in the range 19 − 21 it’s possible to apply another breakpoint just double clicking on the left
part of the line as previously done. Checking the register window it’s possible to check the effects of the operations.
Another way to improve the debugging at instruction level is to click on the button called instruction stepping mode shown in Figure 50c
(indicated with a blue i). When this mode is enabled (the surrounding of the i becomes light blue), clicking on the command step into
indicated by cursor in Figure 50c it’s possible to check the effect of the instruction one by one.

23
Section 11 : Data types to manipulate floating point data
In the previous sections it was discussed how to represent integer numbers in C. In this section the same topic will be extended to
additional data types to represent real number with better precision.
A decimal, or real number, contains a decimal point (for example 12.55). In computer memory, the real numbers are stored according to
the representation standardized by the standard IEEE 754. The IEEE 754 floating point representation is an approximate representation
of real numbers. All computer systems and microcontrollers nowadays use this standatd to store real numbers in memory. If youre are
working with numbers that have a fractional part or in case you are using integers that don’t fit into a long data type, it is possible to use
floating point representation.
Let’s see when practically these data types could be useful in real application. Let’s suppose to store a very small number as the charge
of an electron(1.60217662 · 10−19 coulombs). The same problem could be present when a very big number has to be represented as
distance between earth and andromeda galaxy (2.3651826 · 1019 Kms). Summarizing floating point data representation is needed to
store:
• a too small number in memory
• a too big number in memory

• a number with many many digits after the point


It is worth to investigate more about the IEEE-754 floating point standard. Let’s suppose to represent the number +7.432 · ·1048 . A
lot of memory would be needed to store this number as it is. This standard allows to approximate the number saving a lot of memory
saving just the significant informations shown in Figure 51. It is worth to notice that the number in Figure 51 is expressed in scientific
notation.

Figure 51

As it’s possible to see in Figure 51 it is necessary to store :


• the sign of the number
• the mantissa representing the integer part of the number
• the exponent

There are two ways to approximate the number in scientific notation shown in Figure 52.

(a) (b)

Figure 52

The single precision representation shown in Figure 52a uses a 32bits are reserved to store the information while 64bits are reserved
in the case of double representation . The approximation is more accurate in the case of double precision representation obviously.
Another example could regard to store in memory the number 125.55. As it’s possible to see there is an integer part (before point) and a
fractional part (after the point). Using the already mentioned types for integer numbers as int, char, long just the integer part would be
stored and the fractional part would be completely lost.
The new format specifiers for single and double precision data types are :

• %lf format specifier to read or write double type variable


• %f format specifier to read or write float type variable
• %e , %le format specifier to read or write real numbers in scientific notation

24
All constant with a decimal point are considered as double by default. The main properties of the data type float and double are
shown in Figure 53.

Figure 53

Let’s see some examples (Figure 54).

(a) (b)

(c) (d)

(e) (f)

Figure 54

In Figure 54a it was used the format specifier for float number. As it’s possible to check in Figure 53 this allows to show up to 6
decimal places. In the ouptut in Figure 53a indeed 6 decimal places after point are shown, some decimal places of the float number
defines were lost then. If the the format specifier is modified as in Figure 54b it was indicated to show up to 9 decimal places as it
was done in the output. It is worth to notice that just the first 6 decimal places are correct, the other are wrong comparing the result
with the number definition. It is worth then to try to adopt double data type to improve the precision (Figure54c). Double data types
allows to represent decimal number up to 15 decimal places as it’s shown in Figure 53. The result in Figure 54c is correct indeed, all
the decimal places are correct now. It’s possible to show the same result in scientific notation as well (Figure 54d), as it is known the
term e+01 indicates the decimal part multiplied for 101 . Let’s return to the problem exposed in the beginning of the chapter, it is worth
to try to represent the charge of electron in our program (Figure 54e). Float and scientific notations specifiers were used, the value 0
was the output in the case of float specifier. This make sense because the compiler try to show the number as 0.00..., the float specifier

25
limit decimal places up to 6 as it was mentioned. Another problem is that there is a loss of information trying to use the float data-type
to chargeE variable definition due to the just mentioned limitations. Trying to use the double data type for variable definition and for
format specifier and increasing the decimal places shown it is possible to see the correct desired output.

Section 12 : Taking input from the user adopting scanf command


Scanf is a standard library function that allows to read input from standard in. Standard in is generally the keyboard. By using scanf
library function you can read both characters and numbers from the keyboard. An example of the usage of scanf command is shown in
Figure 55a.

(a) (b)

Figure 55

It’s possible to see a first part defining the input desired and a second part indicating the address in which the data will be inserted.
The two cases in Figure 55 show two different datatype adopted.
Another library function is the getchar(). If just a sigle character is required to be inserted from keyboard it’s possible to use this
command. getchar() function takes no argument as input and just return int value relative to ASCII value of the key pressed.
An example of getchar() is shown in Figure 56.

Figure 56

Let’s show a practical example how to use scanf library function realizing a software performing the arithmetic average of 3 numbers
inserted from the user.

(a) (b) (c) (d)

Figure 57

In the program written in Figure 57a 3 variables have been defined to store the input numbers from the user, another variable is
needed to store the result of the average. The first printf informs the user he has to insert three numbers in order to obtain the average.
It is worth to notice that the format specifier defined in single scanf function is the same of the relative variable defined. Another printf
shows the result of the operation.
Running this program without the usage of fflush(stdout) it’s possible to not see the first printf function. Let’s try to understand what’s
happening here. Observing Figure 57c, it’s possible to understand that in our case the content of the printf function is not passed directly
to the console output (display in the figure). The content is first stored in an intermediate buffering unit (in red in Figure 57c). .The
compiler has not reason then to send the content of the buffering unit to display before the user inputs are inserted (in the previous cases
the returning value of the function was the trigger). Using fflush(stdout) the value of the buffer is sent manually to the display. stdout
stands for standard output, the result is now correctly send to the console (Figure 57c). The command has to be repeated after each printf

26
function before the compiler has received the input numbers in order to display the relative contents.
Another way to launch the program is follow the path (right click on project → Properties → Resources), open the project foleder, the
Debug folder. A executable file with the name of the project will be found, let’s launch it. Let’s suppose to not have inserted yet the
commands getchar() at the end of the program in Figure 57a. Let’s discuss what happens here, the program calculates correctly the
result but it doesn’t hang to show the result. It just calculates the result and it’s going to be closed. The user won’t have chance to see
result then. The simpler solution would be to impose the program to wait key to be pressed in order to close the software.
It’s possible to add one getchar() command in the end of the program, the programmer expects the programs should wait one key to be
pressed but it doesn’t happen in this case. It’s necessary to see what’s happening in buffering memory here (Figure 57d). In this case the
first scanf detect the first input number, store it in memory buffer and send it to program, at the second statement it charge the memory
buffer with second number and so on. After the third statement of scanf the memory buffer is not clear, this happens because the unread
value
n present in the printf functions kept memorized in the memory buffer. The getchar() commands notices that something is present in
the buffer and allows the program to be quitted. A possible solution would be add another getchar() command as it was done in Figure
57a. There are others solutions which will be shown furtherly during the course.
The 3 scanf commands present in the program in Figure 57a can be resumed using just one scanf command

Figure 58

This solution is for sure more effective and compact considering that more inputs are needed in most of the practical cases.
It’s worth to show a last example of usage of scanf with another exercise.

(a) (b)

Figure 59

The target of the exercise is shown in Figure 59a. The implementation is shown in Figure 59b.What changes compared to previous
cases is the char datatype chosen. It’s possible how compact looks a program using a scanf commands with multiple purposes. The
output is shown in Figure 59b on the bottom part (console view).

27
Section 13 : Pointers in C language
Pointers are one of the essential programming features which are available in C. Pointers make C language more powerful. Pointers are
heavily used in embedded C programming to:
• Configure the peripheral register addresses
• Read/Write into peripheral data registers
• Read/write into SRAM/FLASH locations and for many others things
It’s worth to remember what’s a pointer. The registers, as we know, are characterized by a address number. The pointers are the
memory address locations to identify the registers in memory, this is well exaplained in Figure 60 where pointers are shown on the left
of each register. The pointer, as the name says, points to data.

Figure 60

The definition of the pointer is then always strictly connected to the content in the memory location.
This means that the main operations relative to pointers are:
• read from the memory location identified by pointer
• write content in the memory location
• increment the pointer (going on the above register in Figure 60)
• decrement the pointer (sliding below register in Figure60)
It is necessary to remark (bottom part of Figure 60) that the pointer size, on a 64 bits machine, is 8 bytes (64 bit).
It’s possible to define a pointer similarly as it happens for a variable in the program.
The formal definition of a pointer is shown in Figure 61.

Figure 61

Before to introduce it’s possible to list the pointer datatype available.

• char* • unsigned char*


• short int* • unsigned short int*

• int* • unsigned int*


• long int* • unsigned long int*
• long long int* • unsigned long long int*

It’s possible to notice here that the pointer datatype are exactly the same of the variable datatype already seen previously, they’re
characterized adding a star. One of the simpler operation is trying to assign a constant to a pointer.

(a) (b)

Figure 62

28
The first idea would be to define a pointer as shown in Figure 62a. From the compiler point of view we’re trying to assign a costant
number to a pointer char datatype, the compiler will notice a size mismatch (long long int constant vs char pointer) and it is necessary
to specify to compiler which the term on the right of the equation is a pointer. It’s possible to apply, as for variable, a explicit casting. In
the pointer definition it’s always important to add the star present on pointer datatype.
Let’s go a bit more in details about the definition of Figure 62b. The compiler reserve 8 bytes (for 64 bits machine) of memory for the
definition of Figure 62b. It is worth to remark that the compiler will always reserve 8 bytes independently from the pointer datatype
(which could be char*, int* etc etc). In other words, the pointer datatype doesn’t control the memory size of the pointer variable. The
pointer datatype identifies the behaviour of the operations carried out on the pointer variable. The operations, as previously mentioned,
are read, write, increment or decrement. It’s important make some examples to clarify the concept.
• Example 1 char* address1 = (char*) 0x00007FFF8EC3824

• Example 2 int* address1 = (int*) 0x00007FFF8EC3824


• Example 3 long long int* address1 = (long long int*) 0x00007FFF8EC3824
The difference between the definition on the 3 previous examples is that Example 1 defines a pointer variable to char type data (or
pointer variable to 1 byte data). In the same way, Example 2 defines a pointer variable to int datatype (or 4 bytes data), the third case
shows a definition of a pointer variable to long long int type data (or 8 ytes data).

Read and write operation on pointers


It’s possible to show how to read from a pointer memory location or write in that memory location. The formal read operation is shown
in Figure 63a. The operation in Figure 63a is called deferencing a pointer to read data, this operation is performed introducing a "*"
before pointer variable. This read operation will take the content of the memory location indicated by the address (value in hexadecimal
in top part of Figure 63a) and it will assign that value to the variable char data just defined. An important consideration is that only
1 byte will be reserved in memory for the variable. This happens because the pointer was defined as a char pointer. Figure 63b helps to
clarify what happened in this read operation.

(a) (b)

Figure 63

In this case the particulare value 0x0007F F F 8E3C3824 was assigned to the pointer. When the de-refereincing is performed, the
compiler will pass to the register with the memory location address number equal to the content of the pointer (the above mentioned
constant), the content will be in this case 0x85. The value assigned to the variable will be then 0x85. Let’s move forward and focus now
on write operation on a pointer.

(a) (b)

Figure 64

The formal definition of write operation to pointer is shown in Figure 64a. This operation is called de-referencing a pointer to write
data. To understand how it works let’s observe Figure 64b. It can be seen in the example that the memory location pointed from pointer
present 0x85 as content. The command *address1=0x89 replaces the content of the memory location fixed by the pointer. It can be seen
that now the memory location content was replaced with the desired one (0x89). To see another example it’s possible to observe the
exercise given in Figure 65 and its implementation. The result is shown in the console view on the right section in bottom part.

29
Figure 65

It’s worth to remark that the pointer definition can be found in these two equivalent forms:
• char* p1
• char *p1
Both forms are correct but the second one is a lot more common in books, it’s advisable to use this one to avoid confusion then. It’s
important now to focus on the effect of the pointer datatype influence on write and read operations on pointers.

(a) (b)

(c)

Figure 66

In the case of Figure 66a it’s possible to see that the programmer is trying to assign to a char pointer a long long int value. Obviously,
the compiler will be indicate a mismatch data during compilation. It’s easy to understand that in this case an explicit casting has to be
done to avoid the problem. The casting can be done as in Figure 66b (line 12), in this case the address of long long int is converted in
*char type to avoid the mismatch. Focusing on the effect of the printf at line 14 it’s possible to see that just one byte of data is printed (45
is printed). This happens because the pointer was defined as char* datatype. Changing pointer datatype at line 15, 20, 24 it’s possible to
see that the printed value changes due to different maximum representable values connected to datatype.

30
Section 14 : The importance of the library < stint.h >
The library < stint.h > is the standard library available in C language. It was mentioned before that datatype size depends from the
type of compiler used (the int datatypse size could be equal to 4 bytes for some compilers while it could be 8 bytes for other compilers
for example ). This obviously implies portability issues in the code. In C language the most used datatype which are int and long
cause portability issues. The reason is that storage size for the two above mentioned types is not defined in C standard (standard C90
or C99). The standard defines just the minimum widths of the datatype storage size, the compiler defines the storage size due to the
hardware capabilities of the target platform. One of the reason could be the most efficient data manipulation capability for a fixed target
architecture system. It’s worth to show an example which let understand how big could be the effect of portability issues (Figure 67).

Figure 67

Let’s suppose to have two different compilers, the first compiler present storage size for int equal to 2 bytes. The count will reach
the value (216 − 1 = 65535) and the count will be restarted. The other compiler will present 4 bytes for the same datatype. This mean
that the count then will be restarted at (232 − 1 = 4294967296). In the case of the 2 bytes compiler the code in the if will never be
executed because the variable count cannot go over the value in the if condition. The question then is now: how to avoid these type of
portability issues. The standard library header file < stdint.h > defined fixed width integers using alias data types for the standard data
types avaialble in C. A fixed width integer datay type is an aliased data type that is based on the exact number of bits required to store
the data (Figure 68).

Figure 68

The names on the left column are called alias name for the standard dataype. The prefix u stands for unsigned datatype. In this case
the programmer will fix the exact number of bytes for that datatype, the portability issues are gong to be solved then. These unified
datatype definitions will be always written in the library < stint.h >. An example of path for Windpws OS could be (C: →Mini-GWW
→ Include ). It’s possible to find here all the library, the standard library above mentioned will be present. The content of standard
library will be shown as in Figure 69.

Figure 69

The standard types int8_t and uint8_t are some examples of the unified datatype standard for this compiler. The just mentioned types

31
show a standard datatype for 8 and 16 bytes storage size.
If the standard library < stdint.h > of another compiler will be opened, the content in general will be different. What matters is that
the aliased names for the datatype will be always the same.
The folder path to find our STM32 microcontroller standard library is (C: → ST → textitSTM32CubeIDE → textitplugins → textitgnu-
arm-embedded → textittools→ textitarm-none-eabi → textitinclude) and finally find the standard library here.
It’s important to remark some useful commands present in the standard library.
• uintmax_t defines the largest fixed-width unsigned integer possible on the system
• intmax_t defines the largest fixed-width signed integer possible on the system
• uintptr_t defines a unsigned integer type that is wide enough to store the value of a pointer

Section 15 : Operators in C language


An operator is a symbol that tells the compiler to perform a certain mathematical or logical manipulation on the demands. The most
commnly operators used in C are shown in Figure 70a.
The operators can be classified in unary, binary. ternary due to different number of necessary operand. Oberving the arithmetic operators
it is worth to explain how the modulus operator % works. The modulus operator produces the remainder from division. For example
(14%4 is equal to 2) which is the remainder from integer division.
It is very important to show how operator precedence works. The precedence fixes the order of how the compiler will improve the
operations when many operators are present in one expression. The table in Figure70b shows the order of precedence of many operators.
It’s not necessary to memorize this informations.

(a) (b)

Figure 70

The precedence level for operators will be higher as lower is the precendce value (an operator with precende level 1 will have
precedence than one operator with level 3 for example. ). The propriety of the associativity in the right columns will regulate the
precedence order for operators with the same level precedence. In some cases the bigger precedence proceed from left to right while in
some other cases the viceversa happens.

32
Arithmetic and unary operators
A very important rule to know is that when the programmer wants to put the higher priority to an operators he has to insert the operands
and operator between brackets. Let’s show an easy example to clarify the concept.
• 2+3∗4 (2 + 3) ∗ 4
In the above expression on the left let’s check the operators in Figure 70b. The multiplication operator will present a precedence
level equal to 3 while the addition operator will present a precendece level equal to 4. The result of the above left operator will be equal
to 2 + 12 = 14 because the multiplication will be performed first. If the programmer wants to put priority on addition operator, on the
other hand, he has to write an expression on the above right expression. In this case the compiler will obtain 5 ∗ 4 = 20 because the
addition will be performed first.
It is worth to show another example where more operands with the same level of precedence will be present.
• 4 ∗ 5/2 ∗ 5

The above expressions show two operators (/ and * ) which present the same level of precedence. In the table of Figure 70b it’s
possible to see that they present a precedence level equal to 3. The associativity rule has to be followed to regulate the priority (left to
right for level 3). In this case then the first operation will be the first operator on the left in the expression, the multiplication will be
perfomed then 20/2 ∗ 5. At this step, the first operator on the left will be the division (10 ∗ 5 = 50). The result will be 50 then.
If the programmer wants instruct the compiler to perform operations following usual math rules precedence he has to use brackets. The
new expression will be written as it follows.
• (4 ∗ 5)/(2 ∗ 5)
The compiler here will perform first the expression in the bracket from left to right. It will obtain then 20/10 = 2. The bracket
belong to level 1 of precedence as it can be observed in Figure 70b.
Let’s discuss about unary operators in C, the unary operators needs just one operand as it was previously mentioned. The unary operator
are the increment and decrement operator.

• x++ ;

• x--;

The first above expression represent the increment operator while the second represents the decrement one. The first expression
function could be implemented with arithmetic operator in this way (x = x + 1) but in these case two operands are necessary. There
is another fundamental classification to remark for unary operator in C. For example the increment operator will be analysed (the same
speech is valuable for decrement operator as well). Let’s take these two expression
• x++ ;

• ++x ;
The first above operation is called post-increment and the second one pre-increment. Both commands increment the variable x by 1
but the result will be different. It’s worth to show an example to clarify the concept.

Figure 71

In both of cases in Figure 71 the programmer would expect that both y and m variable would be equal to 6 but this happen just for
the pre-incrementing. In the pre-incrementing the compiler first increment x variable (x=6) then the assignment is performed (y=x). In
this case it is obtained y = x = 6. In the post incrementing the assigment will be the first operation performed by the compiler, in this
moment (x=5), this means that the compiler will perform (y=x) and due to the actual value of x (y=5). The increment will happen after
(x=6). The assignemt was already done and y = 5 is kept valid.

33
The discussion now will be focused on pre-increment and post-increment on pointers. A pointer increment can be observed in Figure
72.

Figure 72

As in the case of the variable both arithmetic or unary operators can be adopted to increment a pointer. In Figure 72 a pointer is
defined as 32bits = 4bytes. When the increment is performed, the programmer has to remember that the pointer is a 4bytes pointer.
This will increment the defined pointer by 4 and not by 1. The main difference, compared to the variable cases, is that the increment
coefficient depends on the pointer size fixed.

Relational operators
Relational operators do some kind of evaluation on the operands and then return value 1 for true and 0 for false. Relational operators
are binary operators and require two operands to work. The relational operators are evaluated in order from left to right. The relational
operators in C are shown in Figure 73.

Figure 73

It’s worth to remark that relational operators to check if two variable are equal use the == notation differently than = used for the
assignment operation (Figure73). The just mentioned operation will return then 1 if the two variable are equal and 0 if they’re different
(due to the relational definition in the beginning of the subchapter).
In C language the value 0 is interpreted as f alse while anything non-zero is interpreted as true, a negative value then could represent
true condition as well. The relational operators are commonly used in if and while statement as it will be seen further in the course. To
clarify even better the concept a practical example is shown in Figure 74.

Figure 74

34
Logical operators
Logical operators mame is referred to Boolean logic operators in C language (and, or, not). The syntax of logical operators in C is shown
in Figure 75a.

(a) (b)

Figure 75

The return value of the logical operator in C follows the standard truth table of math boolean operations (shown in Figure 75b ).
The and operator return 1 if both operands present non-zero value, the value 0 is returned when one of the operands is equal to 0. When
compiler finds out that first operand is 0 the compiler will return directly 0 as output function then. Let’s observe some examples in
Figure 76. In this case both operands are true because their value is different than zero, the result of the and will be true as it can be seen
in the truth value. As it was mentioned before, if one of the operands was 0 the result would be false or 0.
The logical operator or will be discussed now. In this case the result will be zero or false if both operands are zero(as it can be controlled
in the truth table shown above). It is enough to have one operand within non-zero value then to get a result equual to 1 or true. Similarly
as it happens for the operator and, the compiler produces a or output equal to 1 when the first operand value is non-zero ignoring the
value of the second operand.
The not operand is simpler to understand and use. It simply reverses the state of an operand, if an expression is true or 1 it makes it false
or 0 and viceversa.

Figure 76

35
Section 16 : Decisions making C language
When a program is written, there is always a part of code that is needed to be executed just when particolar conditions are verified. The
code has to react properly due to internal or external events or conditions.
One example could be when user press a key as input then execute this set of statement or perform another operation due to another
key pressed. One pratical example could regard a water level indication system, when the sensor detects water level rising above the
threshold, the program executes the code to turn off the motor. The code won’t turn off the motor otherwise (shown graphically in Figure
77a). In C there are 5 different way to take decisions by making use of below decision taking statement (shown in Figure 77b).

(a) (b)

Figure 77

The if statement
These statements will be analysed one by one further in the course. Let’s begin with the if statement. The precise syntax of this statement
is shown in Figure 78a.

(a) (b)

(c)

Figure 78

In the round brackets the programmer has to insert the condition for which the program has to execute single or multiple statement.
If the condition will be true the program will execute the statement/s, if the condition is false the program will skip the statements inside
the if. Another thing to remember is that the statements have to be always be included in curly brackets when more than one statement
is written. The expression has not to present a semicolon in the end as it is always done for standard code to be executed.
An example is shown in Figure 78b. In the left part of Figure 78b there is a single if statement while on the right part a multiple statement
type. The curly brackets are not necessary in the case of single statement. In the left case, the condition is not satisfied (myData > 40
is false), the program skips the statement inside the if to execute the rest of the code. In the right part, on the other hand, the condition
is true (myData > 40 is true) and the program executes the code inside the if environment. When multiple statements are written the
curly brackets cannot be avoided. If semicolon will be inserted at the end of the if condition, the compiler won’t show error (maybe
some warnings) but the logic of the program will be completely wrong. The compiler will consider the statements inside the if just equal
to a semicolon if something like that will be done. The result of this wrong operation by programmer can be clarified observing Figure
78c. The relational operators discussed at page 33 are very commonly use for conditions inside if and if-else statements.

36
Another very interesting example is shown in Figure 79.

Figure 79

In this case just one variable is used as condition inside if statement. Initially, the variable is set equal to 0. The compiler will find a
false condition inside the if statement (variable _value = 0). The button press on the right of Figure 79 will set variable value equal to
1. The if condition will be true in this case and the compiler will execute the statements. This examples shows that one variable can be
used without anything else as condition for if statement.

The if-else statement


The second decision making statement analysed here is the if-else statement. The if-else statement syntax is shown in Figure 80a, the
single and multiple statements syntax are shown.

(a) (b)

Figure 80

It can be observed a statement else added to the standard if statement discussed above. When the expression in the if statement is
true the compiler will execute those statements as in standard if. When the if condition is false the compiler will execute the statement in
the else block. Figure 80a, in the right section, shows the multiple statement execution case, the curly brackets are necessary then. The
if-else works in the same way executing multiple code due to the condition defined. The wokring mechanism of the if-else statement is
also shown in a block diagram in Figure 80b.

37
The if-else-if ladder statement
The if-else-if ladder statement is very similar to the if-else statement. The syntax of this statment is shown in Figure 81a. The compiler
will check the condition from the top one (in the if statement), it will check the first else-if condition just only the first if condition will
be false. The statement in the last else will be executed just if all the conditions above will be false. If one of the conditions are true, the
compiler will perform the instructions in that block and skips all the other blocks. The block diagrams relative to this statement working
is shown in Figure 81b.

(a) (b)

Figure 81

Conditional operators
The conditional operator is a ternary operator (which needs 3 operands) in C language used for conditional evaluation. The operator
symbol is ?. The syntax of conditional operator is shown in Figure 82. The three operands are shown clearly in Figure 82. The example
in the Figure will be used to clarify how this operator works. The first operand (inside round brackets) is the condition to evaluate. If the
condition is true the second operand instruction will be executed. If the condition is false, the third operand instruction will be executed
by the compiler. This is resumed in Figure 82b. Observing the example in the bottom part of Figure 82a, if the variable defined is equal
to 9 (first operand condition), the result will be a = 5 (2nd operand condition). If the first operand condition is false, the result will be
a = 99 (3rd operand condition).

(a) (b)

Figure 82

It could be useful to adopt the conditional operator to replace if-else statement in more compact modality as it’s possible to see in the
example of Figure 83.

(a) (b)

Figure 83

38
Switch/case statement
The last statement analysed in this course is the switch/case statement. It’s a decision making statement available in C language. It may
used to replace several if-else statements. The syntax of the statement is shown in Figure 84a.

(a) (b)

Figure 84

The expression will be evaluated first, its value is then compared with several cases. The switch body is made then by several cases
and one default case. When the expression value doesn’t match with any of the defined cases the default one will be evaluated. The case
block start with case keyword and end with break.
It is worth to remark that the value defined in the cases has to be an integer value followed by a colon ":" . In the same way as if-else, the
compiler skips the rest of the code when one of the block case statement is executed. One example is shown in Figure 84b. The example
regards a program that takes in input the digit key pressed by the user and stored in a variable. The variable will be fixed as expression
value. If the key pressed is 2 the compiler will execute directly the statement in the case 2 block case. When the case 2 is executed the
compiler finds break and due to this the compiler goes out of the switch body. It’s advaisable to add always a default case.

Section 17 : Bitwise operators in C


The bitwise operators are commonly and widely used in C for the manipulation of the data in the registers. The bitwise operators are
shown in Figure 85.

Figure 85

It’s important to show the difference between bitwise and logical operators with one example.
• && is a ’logical AND’ operator
• & is a ’bitwise AND’ operator

(a) (b)

Figure 86

39
In the example of the result using logical operator the two variables value option are zero or non-zero, or true or false. If the variable
presents a non-zero value it doesn’t matter the exact value for the result. In the case of the bitwise operator the AND will be performed
for each bit of the two operators, this mean that the result won’t be boolean for multi-bit operators. This can be observed in the example
of Figure 86b. In this case it’s possible to see that the AND is performed bit by bit between the binary representation of the two operands.
This difference is valid for bitwise and logical OR as well.
• | is a ’logical OR’ operator
• k is a ’bitwise OR’ operator
The bitwise NOT operator is applied bit by bit as well. For example performing
• A = ’010’ → C = ∼ A → C = ’101’
The logical NOT operator produces just true or false as result on the other hand. In the case of the signed base complement
representation to obtain the opposite number it is necessary to negate all the bit and add 1 to result.
The XOR bitwise operators follows the truth table shown in Figure 87. The operator XOR is present just in bitwise version, there is no
logical XOR. The operator symbol is shown in the following expression
• A∧B

Figure 87

XOR has a truth table similar to OR, the difference regards the case in which both operands are equal to 1, the result in this case is 0.
The XOR operands produces as output 0 when the two operands present the same value and 1 when the two operands present different
value.
The bitwise operators can be used in embedded C program to :
• Testing of bits (&)
• Setting of bits (|)
• Clearing of bits (∼ and &)
• Toggling of bits (∧)

Testing, clearing and setting bits


Let’s focus now on the testing of the bit. To understand what the testing of bits is it’s worth to analyse an example. The example shows
a program able to understand if an input number is even or odd. Figure 88a shows two generic numbers in binary form.

(a) (b)

Figure 88

Considering the least significant bit value (equal to 1) it is clear that the value of this bit could be used to evaluate if a number is
even or odd. The even numbers present a LSB equal to 0 while this parameter is equal to 1 for odd numbers. The main problem now
is identify the value of the lsb for a number with a generic number of bits. These types of problems can be solved used a technique
called Bit Masking. An example of Bit Masking is shown in Figure 88b. The LSB value of a number is tested here. The operator AND is
performed between the number and the value 1. Performing this operation, the AND with zero makes zero all the numbers but the least
one. In the case of LSB, if the LSB = 0 the AND will give 0 as result. If LSB = 1 the result will be equal to 1 considering the AND
truth table.
Bit masking technique (which can be used OR operator too) can be used mainly for two purposes :

40
• modify bit value : if the state of the bit is zero, make it one or make it 0 if the bit value is equal to 1
• test bit value : check whether the required bit position of a data is 0 or 1

The bit masking can be used to each bit position, to see the position of the bit close to LSB a number with all bit equal to zero and
one in that position has to be used. The position of 1 in this case select the bit to test. The example in Figure 88b has shown how to use
AND to clear the desired bits.
To set the bits (make them equal to 1) it’s possible to adopt the operator OR in similar way. Figure 89a shows how to do it, exploiting
OR operator it is enough to OR the bit to set with 1. The OR function gives as result 1 if one of the two operands is equal to 1 indeed.
The clearing of the bit using AND operator is shown in Figure 89b.

(a) (b)

Figure 89

It is worth to show the pratical usage of the XOR opeartor. The XOR operator could be used to toggle LED for example. The
following condition can be analyzed.

Led_state = Led_state ∧ 0x01 (1)


If the Led_ state on the right term will be 1 the result will be 1 ∧ 1 = 0. The next operation then will be 0 ∧ 1 = 1. A Led toggle has
been realized then.

Bitwise shift operators


In this section the purpose and features of the bitwise left shift and bitwise right shift operators are going to be discussed.
The bitwise right shift operator :

• takes 2 operands
• bits of 1st operand will be right shifted by the amount decided by the 2nd operand
• Syntax : operand1  operand2
One example is show in Figure 90a.

(a) (b)

Figure 90

The example implies a right shift of the variable a by 4 positions. Let’s analyze how this command will work. The 1 more on the
right will be pushed on the right and it will be lost. Considering a shift of all the numbers to right by one position it is clear that the bit
more on the left will be empty. All the empty position will be refilled with 0 by default. If this mechanism is repeated for 4 times the
part in red will be shifted by for position and 4 zeros will be inserted from the left of the registers. This means that the final result will
be :
b = 0000 0110
As it was explained the 4 bits on the left were shifted while the 4 bits on the right in a will be lost after shifting. Another similar
example is shown in Figure 90b. The right shift regards 3 position. In this case the last 3 bits will be lost while the bits 3, 4, 5 considering
the bit 0 the LSB will be shifted.

41
The bitwise left shift operator shares all the features with the right one. What changes is the direction of the shifting and the syntax.

• Syntax : operand1  operand2

To show how the operator works the same number as in previous example will be considered.

Figure 91

In this case the bit to be shifted will be the one in red in figure. The first bit to be lost will be the MSB (the one more on the left).
This bit will be shifted on left and lost, similarly to previous case a 0 will be inserted (this time from right). Repeating this procedure for
4 times the obtained number will be:
a = 1111 0000
As it was said the 4 bits will be lost and the shift of the group in red of 4 bits will be replaced with the 4 zeros in the final result. The
right and left shift can be used to multiply and divide by 2.

Figure 92

Each number will multiplied by 2 for each left shift while it will be divided by 2 for each right shift as it’s possible to see in Figure
92. This is a very powerful features of the bitwise shift operators.
The applicability of bitwise operators regard other purposes as well :

• bitwise shift operators are very much helpful in bit masking of data along with other bitwise operators
• predominantly used while setting or clearing of bits

Let’s consider this problem, set 4th bit of the given data.

(a) (b)

Figure 93

Figure 93a shows on the top part the bit masking method used previously. The bigger disadvantage of this method is that we need to
calculate manually the mask depending on number of bits and position of selected bit. This solution is not so practical.
The method shown in the bottom part of Figure 93 regards the usage of the bitwise shift operator. The big advantage of this case is that
it is not necessary to apply complex calculations. The only parameter to select is the number of shift position. In the example, to shift
the bit 0 in the position of bit 4, 4 left shifts were necessary. The complexity of the code is not changed at all, the code is implemented
in one line as well. In this case the OR operator is used because it was needed to set one bit.
This method is very powerful, it can be used indeed to clear bits as well as it’s possible to see in Figure 93b.The method is shown in
Figure 93b. In this case, as we know, AND operator has to be used. To obtain the proper mask then, the 1 can be shifted in the position
to clear. At this point, it’s possible negate the whole register. A register with 0 in the desired position and 1 in the rest will be obtained.
At this point, the AND can be performed to clear the desired bit.

42
Last application purpose is the bit extraction. The bit extraction regards the copy of some values on some selected bit in a new
variable.
Let’s solve this problem. Extract bit positions from 9th to 14th [14:9] in a given data and save it to another variable. The data to be
extracted is shown in Figure 94a.

(a) (b) (c)

(d)

Figure 94

The data to be extracted is placed between two vertical lines in Figure 94a. As point 1 in Figure says, it is necessary to shift on the
right the desired portion until to move it on the extreme right in the register. The result of the shifting is shown in Figure 93b. The mask
has to be realized then. It is needed to clear the not useful bits then. This can be done using AND operator. The proper mask is then
realized in Figure 94c. The AND is then performed, the output is the desired one (the register will present just zeros apart the desired
portion). As it’s possible to see in Figure 94d everything can be implemented in one line. The datatype casting is necessary when the
data has to be passed for example from a bigger to a smaller register. It’s possible to do it without change the value because the cut part
will be made just by zeros on the left part of the register.

Section 18 : Embedded C coding for LED


This chapter will show how it’s possible to enable an LED on our embedded target device. In order to understand how the LED are
connected to the processor of the microcontroller. The following examples will be referred to STM32F4 discovery board. On ST website
let’s select in the menu Resources the file regarding the schematics. The schematic will show LEDs for this type of board as in Figure
95a.

(a) (b)

Figure 95

On the board the LEDs will be indicated as LD 3, LD 4, LD 5,LD 6. These LEDs are shown in User Manual on the board. Observing
Figure 95a, LED green is connected to pin PD 12, LED blue is connected to pin PD 15 and so on. The name PD 12 indicated that it’s
the 12th pin avaible in port D. The same schematic file indicated before show the ports which can be used to connect micro-controller
to external peripheral units. Each port present 16 pins regarding always to STM32 discovery board. Each of this port can be used to
connect devices such as LEDs, display, button, bluetooth transceiver and so on to microcontroller.
It was already mentioned that LED green is connected to the pin PD 12 in this case. It’s clear that in order to control the LED it’s

43
necessary to control the defined PIN, how to do this exactly. The final target will be to contro the I/O PD 12 pin in order to direct a
HIGH or LOW signal to make LED turn ON or OFF.
To control the pins connected to port D it’s necessary to use the periphearl unit GPIO D. It present a set of registers used to control pin’s
mode, state and other functionalities. The peripheral registers of GPIO D are memory mapped.
Memory mapped I/O pins are controlled using peripheral regiters which are mapped on to processor addressable memory locations. In
this section let’s understand where it’s possible to find these addressable memory locations. It’s very important to understand how the
processor communicates with the other units on the board (Figure 96).

Figure 96

It’s possible to see that the central bus connects processors with memory, external peripherals, timers and so on. The central bus
is made by 2 channels : a 32 bit address channel and 32 bit data channel. This means that it’s possible to use 232 different addresses
in order to identify the units reachable by bus. This means that it’s possible use addresses number ranging from 0x0000_0000 to
0xF F F F _F F F F equal to 4 Giga-bytes of addresses. If we imagine that some data has to be transfer from Data memory to GPIOD,
for example, the data will present the address of the exact output pin which has to be used to trasnfer the desired data between the
available pins in GPIO D.
The memory mapped addresses locations are recorded in the reference manual of the micro-controller. Usually it’s possible to find the
memory map in the section 2 of the reference manual. The boundary addresses are indicated here, the GPIO D peripheral boundaries
address will be available browsing the file. It’s possible to find the boundary addresses relative to the different available communication
protocols (SPI, I2C, UART for example). The addresses which we are interested into are the ones shown in Figure 97. These addresses
are valid for the above mentioned board, they could be different for different micro-controllers.

Figure 97

All peripheral registers in STM32 micro-controller are 32 bits wide. Different peripherals have different number of peripheral
registers. You should never assume about the address of the peripheral registers. Let’s always refer to the device reference manual. The
GPIO D type registers are shown in Figure 98a. The same registers are available for GPIO A, GPIO B and GPIO C.

(a) (b)

Figure 98

The mode registers allow to if the pin will be a input or output one (using a button for example). The pull-up and pull-down register
allow to activate or de-activate pull-up or pull-down registers. The GPIO D peripheral registers organization is shown in Figure 98b.

44
The GPIO D is made by a 32 bit register, in order to set or clear the desired bit it’s needed to use bit mask technique on this register
group. To set the bit 0 to high value it’s enough to use a OR operation on GPIO D register (GPIO D OR 0x000 0001 in this case).
The procedure to turn on a LED can be summarized in the following steps:
1. identify the GPIO port (peripheral) used to connect LED, in our case GPIO D

2. identify the GPIO pin where the LED is connected, in our case 12
3. identify the GPIO D peripheral (enale the clock)
• until you enable for a pheripheral, the peripheral is dead and it neither functions nor it takes any configuration values set by
the programmer
• once you active the clock for a peripheral, the peripheral is ready to take your configuration and control related commands
or arguments (configuration values)
• NOTE : for some micro-controllers, the peripheral may be ON by default, you need not to perform any activation then (you
should explore this by the device datasheet for reference manual)
4. configure GPIO pin mode as output : since you are driving an LED (to ON or OFF value), the operation mode of the GPIO pin
has to be configured as output
5. write to the GPIO pin
• 1 (HIGH) to make the GPIO pin state high (3.3 V)
• 0 (LOW) to make the GPIO pin state low (0 V)

The discussion now will be focused on how to activate a peripheral clock in order to enable a peripheral port.

How to enable a peripheral clock


As it was mentioned before, it’s necessary to enable peripheral clock before to use peripheral unit. If this operation won’t be performed
the mode configuration on the pin (input, output etc.) won’t be effective.
The peripheral clock is enabled:
• through peripheral clock control registers of the micro-controller
• in STM32 microcontrollers, all clock control registers are mapped at the below address range in the memory map of the micro-
controller
In memory map there is a element called reset or clock control indicated in manuals as RCC.
In the case of the discovery board the RCC address location range is 0x4002 3800 − 0x4002 3BF F .
This units controls all the clocks connected to the elements on the board, the programmer has to identify now the exact address for its
peripheral unit.
The section 6 is focused on RCC, it’s necessary now to focus on RCC registers in one of the subsections. The progammer is interested in
this case in RCC peripheral clock enable register. The programmer has to identify now the bus needed to connect the peripheral unit. In
order to do that let’s return in the previous section of the memory map of micro-controller. Figure 97 shows the address location of the
GPIO D, on the right column it’s possible to see the name of the bus AHB1. The programmer has to enter in the RCC peripheral clock
enable register relative to AHB1 bus (Figure 99a).

(a) (b)

Figure 99

Figure 99a indicates us how to calculate the address of the peripheral unit to be activated. The address offset has to be added to the
base address of the RCC address (equal to 0x4002 3800 in our case). The RCC range address is indicated in the memory map. The
calculation has to performed as follows. This calculation regards the point 3 in the above list.

45
base address + of f set = 0x4002 3800 + 0x30 = 0x4002 3830
Figure 99b shows that the bit 3 set implies the GPIO D activation (point 4 of the list). It’s necessary to browse in GPIO section and
select GPIO mode register menu (Figure 100a).

(a) (b)

Figure 100

Figure 95a shows that the green led has to be activated setting PIN 12 of port D. Figure 100a identify the PIN 12 mode in the bit 24.
In our case the couple of bits 24-25 value has to be set to value 01 to select output mode (Figure 100b).The offset address is 0x00. The
calculation for base address is then :

base address + of f set = 0x4002 0C00 + 0x00 = 0x4002 0C00

The last menu to check in order to turnon LED is the GPIO port output data register. It will be shown in a view as in Figure 101.
The pin to be set is the PIN 12 as it was already mentioned several times.

Figure 101

The last calculation to be performed will be the following one checking the offset in Figure 101.

base address + of f set = 0x4002 0C00 + 0x14 = 0x4002 0C14


It’s possible to summarize all the calculations performed on the addresses and their purposes in steps observing Figure 102a. It’s
possible utilize finally this data to implement code to turn on the LED.

(a) (b)

Figure 102

It’s possible to implement the code as in Figure 102b. The first parts of the program defines the pointers to the calculated address in

46
Figure 102a. The code in Figure 102b at line 19 indicates this operation :

∗pClkCtrlReg = ∗pClkCtrlReg | 0x08

It’s a compact command to indicate OR operation with the initial value pointed by pointer. The coefficient 0x08 is needed to set the 3rd
bit equal to 1 in Figure 99a. The same rule is followed in all the other commands in the software to set or clear the desired bits in the
registers. The last command at line 30 indicates an infinite loop. This is another compact way equivalent to the form :

while(1){ ; }
In order to check if the application works for our target machine it’s possible to follow the path : Windows → Show view → SFRs.
This menu shows the state of the peripheral registers.
Opening GPIO D section it’s possible to check all the registers of the port (mode, output and so on).

Improve the LED turn on code exploiting bitwise operators


The code in Figure 102b works in proper way but it’s not that valuable in practical usage. The programmer has to perform these operations
continously and calculate everytime the needed constant to set or clear some bits in the register is a slow and very uncomfortable
approach. A much more effective version can be implemented using the bitwise shift operators in section 17. The code in Figure 102b
can be modified as in Figure 103 (just modified part are shown, the rest of the code is still the same).

Figure 103

The first operation allows to set the 3rd bit position just shifting value 1 by 3 position. The command at line 23 is needed to clear
the bit. The constant 3d = 11b , the constant in decimal base 3 is equivalent to 11 in binary base as it is known. The 11 generated is then
shifted in the desired position, it is negate and an AND is performed to clear the desired bits. The other two operations are similar to the
setting operation performed already. This technique simplify all these operation, it’s now necessary just to check the shifting position
needed, it’s possible to avoid the calculation of the constant. The code looks definitely more compact and easily readable compared to
the one in Figure 102b.

Section 20 : Looping in C
Let’s start with an example to explain why looping is very important in C.

Figure 104

Figure 104 shows an example of looping in C. The two codes execute the same instruction, the right column one present a loop
statement. The code looks more effective and compact. It is easy to imagine that most of the time the number of operations to perform
could be much more than 10 (it would be very annoying and useless then repeat the same commands so many times).
There are three types of loop statements in C :

• while loop

• for loop
• do .. while loop

47
The term while , for , do are C reserved keywords.
Looping allows to execute some instructions again and again until some conditions are met, the settings of these conditions is crucial for
the proper working of the loop.

While loop
The first explained loop statement will be the while loop, its syntax is shown in Figure 105a.

(a) (b)

Figure 105

Figure 105a shows the single (right column) and mulitple statement (left column) while loop. In the case of the multiple statement
curly brackets have to be used to insert the instructions to execute in the loop. It can be seen that after while keyword an expression is
defined in round brackets. This condition determines if the statements of the loops are going to be executed. If the expression if the
round brackets is true, the statements will be executed again and again until the expression becomes false. When the condition becomes
false, the program exits from the loop and continue to perform the other instructions written.
Figure 105b resumes the mechanism of while loop using a flowchar block diagram.
It is worth to remark that the programmer should not use any semicolon after the expression in round brackets. This mistake won’t be
recognized during compilation but it will completely changes the logic of the program. Figure 106a shows how the compiler sees the
code when semicolon is used in not proper way in while loop.

(a) (b)

(c)

Figure 106

The compiler recognizes the statements in the body equal to a semicolon, it will perform again and again no operation or NOP. The
compiler then won’t increase the index i in the condition, the condition won’t never become false and an infinite loop will be created.
The infinite loops are sometimes used in embedded programming. One example is shown in Figure 106b. It’s possible to see inside the
body of while the statement (while(1)), the condition is trye and the loop will be executed forever.

48
The forever loop is a particula type of while loop, its standard form is shown in Figure 106c. It is common to see forever loops in C
programming.

Do-While loop
The syntax and the flowchart diagram of the do-while loop are shown in Figure 107.

Figure 107

The main difference with the while loop is that the statements will be executed once in this case, the condition will be evaluated then.
The statements in the body will be executed once no matter the condition defined.
The do-while loop, after this first phase, will work exactly as a standard while loop executing the statements until the condition will be
true. Differently than in while command, the semicolon has to be inserted here after the condition, avoid the semicolon here would imply
a compilation error. The do-while loop is commonly used writing multilin C macros in header file.

For loop
The for loop is one of the most spreadily used loop in C programming. The syntax of the for loop is shown in Figure 108.

(a) (b)

Figure 108

The statement is made by three blocks. The two semicolons are necessary but some blocks could be empty. The blocks of the for
loop can be used as it’s shown in Figure 99.
The for loop works in this way.
• block 1 is executed just once no matter the loop condition
• block 2 is then evaluated
if the expression is true, the statements in the body are evaluated and block 3 is executed
if the expression is false the loop breaks
The block 1 statement will be executed then, from that moment to go on the loop will continue to execute body of the loop and block
3 until the condition in block 2 becomes false. The for-loop flowchart represents this mechanism in grapical way (Figure 99b).
Block 1 is usually used for the inizialization of a index (i = 0 , for example) because this expression is executed just once. Block 2 is
used for a condition on the index ( i < 10, for example). Block 3 is usually filled with increment or decrement of index (i++ or i–).

49
Section 21 : Type qualifier const
There are two important type qualifiers in C :
• const

• volatile
Applying these qualifiers to variable declaration is called qualifying the declaration. The qualifier allows the programmer to add
some features to a variable.
The const type qualifier in C is used to enforce read only features on variables. The following example shows the main difference
between a standard variable declaration and a const variable declaration.

1. uint8_t data1 = 50; here ’data1’ is called variable

2. data1 = 50; ok, ’data1’ value can be modified through the program
3. uint8_t const data2 = 50; here ’data2’ is called constant variable
4. data2 = 50; ok, ’data2’ value cannot be modified because it’s read-only variable

The feature of the variable becomes read-only using const variable. The expression in the example nr 3 above can be analyzed as in
Figure 100a.

(a) (b)

Figure 109

The way to define properly const type is shown in Figure 100b. Both statements are correct. It is advisable to use the second one to
keep the type specifier at the first place. The second (and third as well) example in Figure 100b can be read like this : data1 is a constant
variable of type uint8_t.
By using the ’const keyword’ you’re just making a promise to the compiler that you (the programmer) won’t try to modify the content
of the variable using its name. If you try to modify the variable by its name, the compiler stops you by throwing an error (compile time
error).
You can still modify the content of the variable by using its address. It’s possible to see this in the example in Figure 101a.

(a) (b)

Figure 110

Figure 110a shows a compilaion error because the programmer is trying to assign directly a new value to a const variable. It’s
possible to change the value using variable address, a pointer is needed then. Figure 110b shows how to assign new value to a constant

50
variable using a pointer. A pointer is defined and the variable address is assigned.
The casting is necessary because the stanrdard pointer definition makes the pointer type uint8_t const∗. The address instead has to be
a uint8_t∗ or a type mismatch will occur. Remembering about the datatype matter the compilation is successful as it’s possible to see
in the figure. If the programmer run this code the variable is value is changed properly.
It is worth to remark that const doesn’t mean that the value never changes, its only programming safety feature to ensure that the
programmer shouldn’t try to modify the value.
All const variables are stored in memory in the same way as standard variables. They are placed in RAM. The only difference, in this
sense, is the read-only feature.
All global const variables are stored in ROM or FLASH memory, This also further depends on linker script rules and the hardware on
which code runs.
In STM32 target hardware, all global const variables live in FLASH memory. When you try to modify the const variable using its
address, operation has no effect. The flash memory of the micro-controller is indeed write-protected. It’s possible to verify this moving
the const variable definition in Figure 101b out of the main function to make it global. Executing now this new code on the PC the
program crashes because we’re trying to write in the write protected section of the microcontroller memory. If the same code is run on
the target, the program won’t crash but the operation won’t have any effect.
The programmer should use const keyword to guard the data from modifications in the project. If you unknowingly try to change the
read-only variable, the compiler will alert you.

Const pointer and different case study


The first case was already mentioned, it’s about constant data (point number 1 in the list).
1. uint8_t const data = 50
2. uint8_t const ∗ pData = (uint8_t∗ )0x40000000;
3. uint8_t ∗ const pData = (uint8_t∗ )0x40000000;
4. uint8_t const ∗ const pData = (uint8_t∗ )0x40000000;
The case number 1 is can be used to define mathematical constant in the program as :
• f loat const pi = 3.1415;
• f loat const radius = 4;
• int const number_of _months = 12;
The case number 2 shows an example of modifiable pointer and constant data. Here, the pointer pData is modifiable but the data
pointed by the pData cannot be modifiable (read-only).
It’s possible to say then that pData is a pointer pointing to read only data. The pointer as already mentioned in the example of Figure
110b is needed to avoid datatype mismatch. The expression in case 2 of the list can be read like this: pData is a pointer pointing to
constant data of type uint8_t datatype. Figure 111 remarks the allowed and not allowed operations on this type of pointers.

Figure 111

It is important to understand which could be the purpose of this expression. Let’s observe the example in Figure 112.

Figure 112

51
The function on the left part of Figure 112 doesn’t guard the programmer that he’s trying to modify a constant variable using a
pointer(variable src is incremented in the loop), the compiler then won’t alert the programmer. In the case of right part of Figure 112 the
const attribute is added in the definition of the pointer. In this case the pointer *src is guarded, when the programmer tries to modify its
value the compiler will alert programmer. The programmer will immediately understand that the pointer should not be changed seeing
the expression as in Figure 112 on the right. It is strongly advised then to use the prototype expression adopted in the right part of Figure
112.
Case 3 regards Modifiable data and constant pointer . The pointer pData here is read-only but the data pointed by the pData can be
modifiable. We can say that pData is a read only pointer pointing to modifiable data. Figure 113 remarks and clarify allowed and not
allowed operations on this type of pointer and data.

Figure 113

The purpose of case 3 can be clarified in Figure 113. This case can be used to improve the readability and guard the pointer variables,
the const keyword is placed before the pointer to underline that pointer has not to be modified.
Last case is case 4: constant data and constant pointer. The expression syntax for case 4 is shown in Figure 114a.In this case, both
pointer and data are read only. pData is a constant pointer pointing to constant data of type uint8_t. It’s possible to say that pData is a
read only pointer pointing to read only data.

(a) (b)

Figure 114

A usage example of case 4 is shown in Figure 114b. It requires the status register. Status register cannot be modified, this would
cause unpredictable consequences. It is worth now to summarize the main properties of const type qualifiers.

• It adds some safety while you write code. The compiler warns you when trying to change the value of the const variable.

• Since a constant variable doesn’t change, it has only one state throughout the program. You need not to track its various states
then.
• Improves the readability of your program.
• Use it generously to enforce pointer access restrictions while writing functions and function prototypes.

• It may also help compiler to generate optimized code.

52
Section 23 : Optimization
The Optimization is a series of actions taken by the compiler on your program’s code generation process to reduce:
• number of instructions (code space optimization)
• Memory access time (time space optimization)
• Power consumption
By default, the compiler doesn’t invoke any optimization on your program. You can enable the optimization using compiler flags.
The GCC compiler allows the programmer to choose between several level of optimization. The following list will shows all the
optimization level from the less aggressive to the most aggressive version (aggressiveness indicates how much effort the compiler uses
to reduce the number of code written).
1. Optimization level O0 (selected by default)
• No optimization
• Not recommmeneded for productions if you have limited code and ram space
• Has fastest compilation time
• This is debugging friendly and used during development
• A code which work with O0 may not work with O0+ optimization levels. Code needs to be verified again applying higher
optimization levels.
2. Optimization level O1
• Moderate optimization to decrease memory access and code space
3. Optimization level O2
• Full optimization
• Slow compilation time
• Not debugging friendly
4. Optimization level O3
• Full optimization of O2 + some more aggressive steps will be taken by the compiler.
• Slowest compilation time
• May cause bugs in the program
• Not debugging friendly
All the codes used until now in this course present a O0 optimization level. The proper optimization level for a code has to be found
by trying by the programmer. If the programmer has a much limited hardware, he’s constricted to try to select the most aggressive
optimization level working with the code. A higher optimization level reduces the code lenght but it raise the compilation time and for
sure the effort of the programmer (programmer could need to re-adjust the code every-time that optimization level raises ).
In order to apply different level of optimization it’s needed to follow this path: (right click on the project → Properties → C/C++ Build
menu → Settings → MCU GCC compiler → Optimization).
The default optimization level, as above mentioned, is O0. In the top right corner of the window it’s possible to change optimization
level. As it was previously mentioned, the same code could not work with higher optimization level selected. In this case the code
crashes and the program doesn’t end or gives unexpected results. In order to understand where is the problem it’s necessary to open the
Disassembly section in the Debugging mode and control in the register windows how the data is trasferred to registers. It’s possible to
see the example in Figure 115 which shows a program for pin read of our target. The program works properly for optimization levels
O0 and O1 but it breaks applying O2 level.

Figure 115

53
The problem, as it’s underlined in the figure note, occurs because the compiler tries to reduce the memory hits. The problem here
is that the variable pinStatus is not updated, this means that the program doesn’t read anymore the user input in order to change this
variable. In the statement the condition is (pinStatus=1), the program never goes out from this if because the pinStatus is not updated
and the condition (pinStatus=0) is never reached. In the Disassembly section,the problem is placed at line 08000220, the command cbz
stands for Compare and Branch on Zero. This commands allows the core to jump memory location 08000232 if the condition r0 = 0 is
true. This never happens because r0 content is never updated (r0 stores content of pinStatus variable).
The programmer here has to inform compiler that it has not to do any optimization on the address pointed by pointer p∗ pP ortAInReg
(see the assignment value line on pinStatus in Figure 115). This is possible using the volatile type qualifier which will be analyzed in
the next lectures.

Section 24 : Type qualifier volatile


Volatile is a type qualifier in C used with variables to instruct the compiler not to invoke any optimization on the variable operation(read
or write). It tells the compiler that the value of the variable may change at any time with or without the programmer’s will. The compiler,
then, turns off optimizing the read-write operations on variables which are declared using volatile keyword. Volatile is very much helpful
in embedded system codes. The example in Figure 116a shows a redundant code (one instruction is clearly repeated). The optimization
level is O0 in this case. Seeing in the disassembly code the instruction data1 = 50 is correctly executed. The instruction data2 = data1
is accomplished but the disassembly code underlines that the compiler repeat twice the operation. The instruction data2 = data1 is
clearly redundant here.

(a) (b)

(c)

Figure 116

Figure 116b shows how compiler reduces the instruction in disassembly section using a optimization level O1. Analyzing better the
code indeed it’s true that two variable were defined but not both variables are used.
It’s possible to see evaluate the effect of the volatile data type in Figure 116c. In this case, the programmer forced the compiler to not
apply any optimization on the memory instruction relative to these variables. The code was analyzed again using O2 optimization level,
in this case volatile type qualifier increases the number of instructions in disassembly mode as exepcted.
Let’s clarify when it’s proper to use volatile qualifier. A variable must be declared using a volatile qualifier when there is a possibility of
unexpcted changes in the variable value(this scenario is shown in the pin-read program exposed above). The unexected changes in the
variable value may happen from within the code or from outside the code (from the hardware).
Volatile qualifier is used commonly with the below scenarios:

1. Memory-mapped peripheral registers of the micro-controllers


2. Multiple tasks accessing global variables (read/write) in a RTOS (parallel protocol standard to transfer information) multithreaded
application
3. When a global variable is used to share data between the main code and an ISR (interrupt service routine) code

54
The pin-read case can be included in case 1 of the list.
Let’s focus now more on volatile syntax and different cases.

1. Volatile data
• uint8_t volatile my_data;
• volatile uint8_t my_data;
2. Non-volatile pointer to volatile data

• uint8_t volatile pStatusReg;
3. Volatile pointer to non-volatile data

• uint8_t volatile pStatusReg;

4. Volatile pointer to volatile data



• uint8_t volatile volatile pStatusReg;

As in the const case, the advised form in case 1 is the first one because it improves the readability of the declaration. Both expression
are correct.
Case 2 shows the case in which there is a volatile data and a not-volatile pointer. This is a perfect case of accessing memory-mapped
registers. Use this syntax generously whenever you are accessing memory mapped registers in your micro-controller code.
The expression can be read like this: pStatusReg is a non-volatile pointer, pointing to volatile data of type unsigned integer_8. Case 3
and case 4 are rarely used, the expression in case 3 can be read as this: pStatusReg is a volatile pointer, pointing to a non-volatile data
of type unsigned integer_8. The expression in case 4 can be read as: pStatusReg is a volatile pointer, pointing to volatile data of type
unsigned integer_8. Case 3 and 4 won’t be analyzed because they are very rarely adopted.
The keywords const and volatile can be applied to any declaration, including those of structures, unions, enumerated types or typedef
names.
Let’s return on the pin-read case shown in Figure 115. The problem was focused on the pointer ∗ pP ortAInReg. The solution then is to
apply volatile before this pointer, the compiler won’t optimize any read or write operation performed on this pointer now. In order to be
sure to solve the issue, considering the peripheral mapped case, it is advisable to use generously volatile keyword and apply it to all the
pointer defined. The just mentioned solution requires the case 1 in the list placed at page 48. Let’s discuss about case 3 (interrupt service
routines or ISR). The program shown in Figure 115 regards a pin-read program developed for STM32 discovery development kit. This
program is not suitable for other types of board because the pin addresses will be different in that case.

(a) (b)

Figure 117

The program will be formed mainly from the two functions shown in Figure 117a. Figure 117b shows a function which set variable
g_pressed when a button on the board is pressed. The main function will count the times in which the button is pressed.

55
The program works perfectly for optimization level O0 (Figure 118a). The optimization level will be set to level 1 now and the effect
will be evaluated. The result is shown in Figure 118b. The data console in the bottom of the Figure shows the result obtained by the
programmer pressing the button two times. The left bottom column in Figure 118b shows the first try, the programmer expect to see that
button is pressed one time but program shows that button is pressed 18 times in the first case and 4 times in the second case (bottom
right column). The program doesn’t work in both times by the way. This means that the higher optimization level breaks the code.

(a) (b)

Figure 118

Usage of const and volatile together


It’s possible to use both const and volatile qualifiers in a variable declaration as per your goal. Think that const purpose is opposite to
volatile one is wrong, the two qualifiers cannot be compared directly. It’s easier to remark this focusing on some examples.

1. uint8_t volatile const pReg = (uint8_t∗ ) 0x40000000;

2. uint8_t const volatile const pReg = (uint8_t∗ ) 0x40000000;

Case 1 in the list can be read as pReg is a const pointer pointing to volatile data of type uint8_t. In this case const is used just to
guard the pointer from unexpected changes from the programmer. The volatile is used to tell compiler that data pointed from right term
side of assignment could receive unexpcted changes, the compiler has not to apply any optimization on that data then.
Case 2 in the list can be read as pReg is a const pointer pointing to volatile const data of type uint8_t. This could souond a bit confusing.
This qualifier (const+volatile) on data means that data pointed by the indicated address is volatile (subject do unexpected changes) but
the programmer shouldn’t change the data pointed to that address (const purpose). In this case const is useful to guard the programmer
and volatile is for the compiler.
Case 2 is the case of reading from read-only buffer or address which is subjected to unexpted changes, the programmer should just read
and not write on that register. In this case the content of the register could still be modified but by external causes (button pressed by
user or external words coming from network for example).

56
Section 25 : Structures and bit-fields
Structure is a data structure used to create user-defined data types in C. Structure allows us to combine data of different types. As usual
the first thing to do is to show the basic syntax of structure in C. The syntax of structure is shown in Figure 119a.

(a) (b)

(c)

Figure 119

As usual, in order to better understand when it’s useful to adopt a structure an example is shown in Figure 119b. The structure doesn’t
take any memory storage. It’s just a description. The structure just defined can be filled with variable datatype of different kind. Let’s
go further and see how to create a structure variable.
The syntax relative to the creation of a structure variable is shown in Figure 119c. As syntax it’s very similar to a normal variable
definition, the datatype is mentioned first, the name follows the datatype as usual.
In the case of Figure 119c, the variable datatype is struct CarModel, a user defined datatype. When structure variables are defined some
memory will be used. The amount of memory used to store this variable is the sum of the single datatype defined inside struct definition.
In the case of the example, the amount of memory will be evaluated as it follows.

memory stored = 4 bytes + 4 bytes + 2 bytes + 4 bytes = 14 bytes

The dimensions are relative to the above mentioned datatype in struct CarModel. Let’s see now how to initialize the variables defined
inside a struct datatype. The following example will show how to initialize variables inside two CarModel structures in this way:

1. 2021, 15000, 220, 1330


2. 4031, 35000, 160, 1900.96

The values in the above defined list have to be assigned to variables carNumber, carPrice, carMaxSpeed, carWeight inside struct
CarModel structure. There are basically two techniques to perform initialization of variables.

57
The first technique shown in Figure 120a is typical of C89 standard method. In this case the order is fundamental and the variable
initialization is applied from top to bottom in the struct defined. The second type of initialization shown in Figure 120b allows to define
precisely the value of a variable in the struct (C99 standard). The order in this case is not fundamental.
When a structure variable is created, use a dot operator to access the member elements. This type of technique can be used in printf
command as well as it’s shown if Figure 120c.
dot operator usage for structure allows to operate on a struct variable in the same way as a normal variable. The assignment of a new
value to the variable can be performed indeed as it’s shown in Figure 120d.

(a) (b)

(c) (d)

Figure 120

Aligned and un-aligned data access on structures


For efficiency, the compiler generates instructions to store variables on their natural size boundary addresses in the memory. This is
also true for structures. Member elements of a structure are located on their natural size boundary. Let’s explore about the natural size
boundary on different kind of known variable datatype (Figure 121).

Figure 121

The natural size of a char datatype is 1 byte, this data-type variable can be placed then at any data address (each type address is
relative to 1 byte register). The short type variable size is 2 bytes, in this case the address number of two consecutive variables cannot
be consecutive, it has to be placed after two bytes to make space to variable in memory. The same can be said for int datatype which
natural size is equal to 4 bytes. This type of storage which always respect the natural size of variables is called aligned.
In order to clarify the difference between aligned and un-aligned storage by compiler it’s useful to analyze one example (Figure 122).

(a) (b)

Figure 122

58
Figure 122a shows the definition of the struct, the main program to show how compiler store variables due to their sizes is shown in
Figure 122b.
The assignment of values is applied to each struct member after the definition of the structure. A pointer is defined to store the address
of the struct variable just defined. The increment of the address will let us know how the compiler store the elements in the structure in
memory. The pointer datatype just defined will be, as default, struct DataSet*. In ordet to avoid problem a casting has to be performed
then. The for loop is defined to show the pointer address value after each value assignment. The printf will print first the value assigned
and after the value of the address pointed by the pointer. Another printf out of the loop will show the number of the bytes used by the
compiler to store the whole structure variable. The output of program in Figure 122 is shown in Figure 123.

Figure 123

The program output is showing us that the memory storage used by compiler is equal to 12 bytes but it is not exactly what we were
expecting. Observing the struct DataSet defined, the number of used bytes should be equal to the following calculation.

SizeDataSet = 1 + 4 + 1 + 2 = 8 bytes
The compiler, on the other hand, uses 12 bytes, the reason why this occurs depends on the aligned data-type storage.
Let’s analyze step by step the output. The first value (11) is assigned to a memory address, the char datatypes takes 1 byte of memory as
already mentioned. The second value, in exadecimal notation (0xFFFFEEEE), is assigned to a int datatype which should take 4 bytes of
memory. It’s possible to see that 3 register location are skipped before to store the value of new variable. The memory address where
new datatype value start to be stored end by 8. The compiler stores data in memory according to its natural size boundaries. Looking at
Figure 121, the memory storage for int variables follows this order 0, 4, 8, | : ect... The compiler then cannot start to store at memory
address ending by 5 but it has to pass to memory address ending with 8 to start the effective storage. In this case, 3 bytes of memory
location are wasted then. The next value (22) will be assigned to char data-type, it can be assigned to any address location, there are
not wasted bytes indeed in this case. Last value to be assigned has to be inserted in a short variable. Short variables can be stored to an
even memory address (each 2 bytes) but in this case the memory location address is odd. The compiler then has to jump to the next even
address available, another byte is then wasted.
This type of data stored is called aligned as already mentioned. The aligned data storage make a lot easier write and read operations
on data by the processor, the reason will be clarified furtherly with another example. Using un-aligned memory storage a lot more
instructions will be executed by processor.
Let’s understand now how to make this kind of calculations without using a program and show the main difference between un-aligned
and aligned struct data storage in C.

(a) (b)

Figure 124

Figure 124a shows the previous case manually evaluated (aligned data storage without structure padding). The considered registers
store 4 bytes or 32 bit. In this case due to alignment the data storage with red cross are lost. Three registers are used, 12 bytes are
necessary in the case of aligned data. Un-aligned data present a lightly difference syntax, it allows to save 4 bytes. This type of un-

59
aligned packed structure storage is not advisable because it force compiler to use more instructions and making harder the work for the
processor. It’s worth to show the difference using disassembly mode for out target machine.

Figure 125

As it’s possible to check in disassembly code the instructions in assembly str and strh apply some renstrictions on the data storage
modality. The str command allows data storage just if the data is word aligned (each 4 bytes), the strh allows data storage just if it’s half
word aligned (each 2 bytes). It’s possible to see how regular it looks the disassembly code for several storage assignments in memory.
The code now will be modified to use the packed way to allow un-aligned data-storage.

Figure 126

In the case of Figure 126, the first data to be stored (0xAA) looks almost the same because it is byte aligned. The situation changes
evidently from second instruction, in this case the memory storage is not aligned and the number of instructions for the same operations
increases in significant way. To store a word in memory the command str could be used, it’s not possible to use it here because data
storage is not word aligned as address. The same str command used before has to be divided in multiple strb commands. The processor
then has to communicate multiple times with memory compared to aligned data storage. This decrease the processor performance. Data3
is a char data variable and it’s aligned in memory. The number of instructions then decrease to 3 as in the aligned memory storage.

60
Typedef command
Typedef keywords in C is used to give a alias name to primitive and user defined datatype. The example in Figure 127a shows the syntax
and the purpose of the typedef usage.

(a) (b)

Figure 127

Figure 127a shows then a comparison between the usage of standard struct keyword and the usage of the typedef on struct. Typedef
allows to define a struct variable without using struct keyword. What matter is to give a name to this new datatype defined by the user.
In this case the datatype name is CarModel_t. The subscript _t is needed to let understand the programmer that the definition regards a
typedef variable and not a enum one.
The enum keyword allows a programmer to defined an enumeration datatype in C. It is mainly used to assign names to integral constants,
the names make a program a lot easier to read and mantain. A program is shown in Figure 127b to show the purpose of enum datatype.
The datatype allows to assign consecutive numbers to the variables. The variables days, part of week, will be assigned automatically as
it follows M onday = 0, T uesday = 1, W ednesday = 2 and so on. The program will see them as number but the programmer can
manage this variables using their names. When enum variables, the subscript _e is typical used.
Missing the subscript will create confusion and programmer won’t understand if the variable belongs to typedef or enum
The missing of the subscripts in the name doesn’t produce compiler warnings or error, it’s for readibilty purpose and it’s highly advised
to use this suffixes in names of variables.
It’s worth to remark that a structure type cannot contain itself as member, whit will generate compilation errors. Structure type can
contain pointers to their own type. Such self-referential structures are used in implementing linked lists and binary trees.

(a) (b)

Figure 128

The allowed and not allowed definition are underlined in Figure 128a.
Another possible way to implement struct in C is nested structure, a nested structure is a structure inside a structure(Figure 128b). In
the example in Figure 128a one of the element in the primitive struct Data belongs to datatype struct as well. moreData represents a
member element name then, it represents a tag name, not a typedef definition name.

61
Structure pointers
In this section, it will be show how to create a pointer variables of a structure. The operations of reading and writing data with members
elements using structure pointers will be analyzed. First, let’s return to the output program example shown in Figure 123. This program
explained the memory storage mechanism of a struct variable in memory, the base address of the struct variable and of the first element
of the struct as well is the one ending by 4.
Let’s suppose that you have been given with the base address of a structure variable and asked to change the member element values,
how is it possible to proceed in this case? It’s necessary to change the base address of the struct variable then.

(a) (b)

Figure 129

A Structure pointer operator is defined as in Figure 129a. Another command is then necessary to allow the pointer to point to the
base address of the structure variable address. In order to modify the structure pointer variable the arrow operator has to be used. It’s
worth to remark then when it has to be used dot or arrow operator in structure, this is shown in Figure 129b. Let’s see a practical example
where it’s possible to apply those concepts.

(a) (b)

Figure 130

Figure 130a shows a write operation using a structure pointer. The strucure pointer *pData is defined and the address of the structure
variable is assigned to it. The initial value of the first element in the structure was equal to 0x11. The two printf show the effect of the
writing operation data using structure variable pointer. In the right bottom corner the output of the program is shown, the program gives
back the desired output.
A clear example of read operation from struct variable pointer data is shown in Figure 129b. A printf command is adopted in order to
display all the elements of a defined struct variable.

62
Bitfield in structures
In order to understand what is the purpose of bitfield in structures it’s worth to show an example (Figure 131).

Figure 131

Let’s suppose to have a 32 bit register and the informations inside it are organized as in Figure 131. Let’s suppose that the programmer
needs to extract the relative bits in order to store them somwehere. Programmer needs for example to extract the LSB and the bit 1 to
get the full CRC information. This operation can be performed using the bit-field technique.
The main problem is that, using classical assignment technique there is a waste of bits in memory unused. Considering again the CRC
packet, a variable uint_8_t0 needs to be defined. The CRC is going to fill just 2 of the total 8 bits available in the new variable defined.
This means that 6 bits are lost, repeating the same speech for all the packets in example in Figure 131 it’s easily understood that a lot
bits are going to be wasted. An example of program implementation using this approach relative to the problem shown in Figure 131 is
shown in Figure 132.

(a) (b) (c)

Figure 132

Figure 132a shows the definition of variables selecting the proper length in bits due to the original packet dimension in 32 bits initial
packet. Figure 132b shows the bits extraction technique above mentioned in Figure 94 at page 41. This tecnique works properly but it
implies some data storage waste.
The bit-field technique allows to inform the compiler how to use the information inside the 32 bit register indicating precisely the bits of
each packet, this new approach is shown in Figure 132c. It’s interesting now evaluate the memory consumption performances between
the two techniques applied. The comparison is shown in the output of two program. Both techniques work for final target, the output is
correct in both ways. The memory consumption is, on the other hand, significantly different. The new technique allows to save 6 bits.
Considering that in most of the cases this operation can be repeated many many times it’s clear that the memory saving using bit-field
technique is quite important.

Figure 133

63
Section 26 : Unions
A union in C is similar to a structure expect that allof its member starts at the same location in memory.
A union variable can represent the value of only one of its members at a time. Let’s present quickly an example to show the main
difference between structure and union in C(Figure 134).

Figure 134

The structure memory addressing works in the same way already shown in Figure 124a (aligned memory storage for structure in C).
In this case the first variable takes 2 bytes (16 bit), 2 bytes are filled by padding and other 4 bytes are used because of the second 4 byte
variable. The total memory size used is equal to 8 bytes then.
The union memory storage takes just 4 bytes instead. The memory storage adopted by union element in C is always equal to the biggest
element in size to in the union (32 bit or 4 bytes in this case). It is worth to remark that changing the keyword struct in union allows the
programmer easily to pass from one to another.
Union gives chance to save memory but it has to be used when the access to its member elements is mutually exclusive. In the example,
if it’s needed to send a packet from transmitter to receiver, it’s used short address or long address modality. It’s never used both, union
can be used in this case (mutual exclusion). Let’s analyze a program to understand which problem a union structure can create if not use
in mutual exclusive case (figure 135).

(a) (b)

Figure 135

Figure 135a shows an example of program in which a union is defined with the short address and long address elements as defined
in Figure 134. In the program two printf commands were defined, the first one should print short address value (0xABCD), the second
one should prints second element value (0xEEEE CCCC). Figure 135a show the output of the program on the right section. The long
address value is displayed properly while the first printf shows a wrong result. This depends on how value are stored by compiler when
union is used. When first printf works it stores the value 0xABCD in the first two bytes of memory. The second printf will take all the
4 bytes of the union (union allows to have biggest in size element as storage) overwriting the previous variable in memory. When the
program ends, the short address will show the least significant two bytes of the long address (due to overwriting) while the long address
value will be properly printed.

64
Applicability of unions in Embedded system code
The union in embedded coding are used mainly for two purposes:
1. bit extraction
2. storing mutually exclusive data saving memory
Let’s return to the example of the bitfield shown in Figure 131 considering the memory organization in figure. It’s possible to use a
combination of union and struct to realize a program that uses less instruction accomplishing same target. Figure 136a shows a struct
nested inside a union. The struct variable was named as PacketField, this modality to name a struct variable is ok when struct is nested
in something else but not when it’s defined normally out of any other structure. This variable size will be the biggest element one (due
to union properties). This means that it will utilize 32 bit in this case.

(a) (b)

(c)

Figure 136

It’s known that union refers always to the biggest variable in size memory area. This means that being careful to define the elements
inside the union from LSB to MSB following the order shown in Figure 131 all the bitwise shifting operations defined in Figure 132b
will be no necessary anymore. The element in memory will be organized as desired due to union modality to store variable value.
The rest of the program can be written as in Figure 136c deleting all the bitwise shift manipulation. When it’s needed to de-reference
elements from union it works as a folder, it’s possible to notice here a struct inside a union. It’s needed to de-reference firs union, the
struct has to be de-reference after and in the end the element can be indicated. This is shown in all the printf in Figure 136c. The program
looks a lot more clear and easy to realize than the one written in Figure 132. The command sizeof can be used to control how much
memory it’s possible to save using struct or unions.

65
Section 27 : Usage of bit-fields in embedded code
It’s worth to extend the already mentioned concepts about structure andunion for our embedded code. Struct and bit-fields are used
heavily in embedded code to bring abstraction to the code. The example of LED toggle will be taken, the example is shown in Figure
137.

Figure 137

As it’s possible to see in the program all the operations needed to turn on a LED are listed. It was necessary, as it’s known, to
clear or set the right bit in order to accomplish the operation. This can be done by a programmer, if the programmer has to project this
application for the user, toggle bit has to be managed to look as a very easy program. The programmer can’t ask user to use micro-
controller reference manual for example.
Observing Figure 99a, it’s possible to see that the memory organization of the peripheral clock register (RCC_AHB1ENR) looks similar
to the example in Figure 131. The abstraction can be applied to our code using typedef structure. The programmer should create a new
header file for the embedded project in order to make the main.c file easier to read. It’s possible to include the header file in main.c using
a command as the following one:

#include ”main.h”
The typedef structure will follow the already seen bit-wise technique to set memory section of registers as desired, the version for
the bus control and for a generic GPIO output data register (ODR) and GPIO mode register are shown in Figure 138.

(a) (b) (c)

Figure 138

Several typedef structures were created for our purpose then. Let’s understand now practically how to use these typedef structures.
Our goal is to configure the peripheral register of a peripheral port. This can be done using its proper address. The GPIO mode typedef
structure is valid for each GPIO peripheral port on the board, it is needed now to adapt to GPIO D.

66
Figure 139, regarding typedef in Figure 138c, shows the pointer needed to point the proper base address of GPIO D indicated in
memory map of the board. When the programmer wants to change the mode the only operation to do is to de-reference the right element
inside the typedef struct using the arrow operator typical of typedef structure pointers.

Figure 139

Figure 139 shows what happen when the typedef structure pointer is de-referenced to change the value of PIN 15. The compiler
operation looks very similar to the bitwise shift manipulations performed manually in the program in Figure 132b. There is always a
internal bitwise shift operation but programmer has not to care anymore about that. The programmer just de-reference the structure
pointer and compiler cares about all these operations.
Figure 140 shows the final version of how the toogle bit program should look adding abstraction using typedef structures.

Figure 140

In this case the user has just to select 0 or 1 value to activate the GPIO desired (GPIO D in our case), select the value for the mode
register. The program in the infinite cycle set the activate of the output data register and for how much time that value will be transmitted
on the selected PIN.

67
Section 28 : Keypad interfacing
In this lecture a 4x4 keypad interfacing will be shown. The keypad will be the input device to our target board. The interfacing will
allow to print the key pressed by user in the keypad matrix.
In order the test the final program it’s needed :
1. 4x4 matrix keypad
2. few jumper wired to do the connections

Figure 141

The 4x4 keypad will present 8 pins (Figure 141) , the first 4 pins are called row pins while the rest of the pins are called column pins.
Figure 141 shows how the columns and the rows are connected. A key is implemented between one row pin and column pin as shown in
top left corner. When no key is pressed the row and column pin are completely isolated (no connection at all). The program then should
detect when a key is pressed where the connection occurs, this will select the right column and row indexing correctly the key pressed.
The column pins (C1,C2,C3,C4) are input pin to microcontroller, the row pins (R1,R2,R3,R4) are output pins for the microcontroller.

(a) (b)

Figure 142

The complete connections view is shown in Figure 142a, Figure 142b shows how the row and column pins result input or output
pins for the microcontroller, 8 free I/O pins are required for this application on microcontroller (as GPIOD in the case of the turning ON
of LED) in section 18. It will be shown that in this case internal pull-up resistors have to be enabled as well. Let’s understand why the
pull-up resistors are required here.
Let’s observe the connection (in electrical terms) between the row R1 and the column C1 in Figure 143a. The pin are called P D8 and
P D0 . The key can be seen as a switch, when the key is pressed which is the state of C1 ? It’s not possible to read the state because the
C1 pin is floating because there is a open circuit (switch opened). When the pin is floating it can pick random noise from the circuit and
it can change its value several times between high value (power voltage) and low value (ground voltage). The voltage read on the pin
could be randomly high or low then, it’s important to avoid this situation then. Pull-up resistors are needed indeed to avoid this type of
situation. The pull-up resistor will be connected in the position in Figure 143a. The pin won’t be floating anymore, the resistor value
should be quite high (around 22 kΩ or more). The pin value will be connected to high value voltage (3.3V for example) when the key is
not pressed.When the key is pressed it’s necessary to connect pin to ground then.

68
(a) (b)

Figure 143

In the end, the pull-up resistors are needed to avoid floating state of the input pins. Let’s understand the basic idea of how implement
the code to detect key pressed.
Let’s remember that when no key is pressed C1, C2, C3, C4 read high value. The scan then will be implemented row by row one at a
time. Figure 144 shows the beginning condition to start the detection.

Figure 144

Output pins have to be exploited,Figure 144 shows that just R1 is low (low active) and then it’s the only active row pin. In this stage
software can detect key pressed in R1 only. If the key 2 is pressed, C2 will be brought to ground state as exaplained before. C2 will be
the only one to present a low value while C1, C3 and C4 will present a high value yet.
This has to repeated scanning each row in the same way keeping other row value high.
The keypad flowchart gives a very precise idea of how to implement the above mentioned program.

(a) (b)

Figure 145

In order to activate the PIN and set them as input or lower it’s needed to check the schematic of our board, detect 8 free I/O pins. It’s

69
needed then to activate them, set 4 of them as input and 4 as output as explained in the example of section 18. Be always aware that the
PINs schedule change from board to board, let’s always refere to the reference manual of your micro-controller.
The only difference is the activation of the pull-up register for input pin, these register are indicated as GPIO port pull-up/pull-down
register. Their activation is fully explained in the manual. Figure 146 shows the pull-up/ pull-down register activation regarding the
manual of ST Nucleo F446RE family.

(a) (b)

Figure 146

Writing the value 01 in the proper register it’s possible to set the registers in pull-up modes as required.
Let’s analyze now more in details the keypad flowchart in figure 145. The first step is the inizialization (all the row value is high). The
infinite loop is needed to control continously if one key is pressed (the user could press key at any time). The first case shows the control
of the column C1 when R1 is low as alread mentioned. It’s important to remark that there is a delay (equal to 200 ms in this case). The
delay is required for the button debouncing. When a key is pressed there will be a metal contact, the metal contact will be kept for an
amount of time (the time in which the user keep pressed the key). Let’s suppose that the time range to press and release the button is
equal to 150 ms. When user press the key the software will detect that the key is pressed, the delay will keep the software busy for 150
ms from the detection moment. If the delay is not present, the software could easily detect that the key was pressed twice. Use for loop
to implement the delay. The process will continue within all the columns for first row set at low level state. The process will be repeated
in the same way for the other 3 rows available.
Let’s make a list to resume all the steps necessary in order to implement the program.

1. Find out the free IOs available on your target pin headers
2. Decide which are the IO pins you are going to use to handle rows and columns of the keypad
3. Create required pointers variables to handel memory-mapped registers
4. Initialize variables with appropriate memory mapped register addresses

5. Make use of type qualifiers such as volatile if memory-mapped registers access is involved
6. Initialization
• Make all row IOs mode as OUTPUT
• Make all colum IOs mode as INPUT
• Activate internal pull-up resistors for all column IOs (refer to the pull-up/pull-down register) (Figure 146a)
7. Implement the key detect logic as per the flow chart
The code for the delay can be implemented using a for loop simply as shown in Figure 147a. Figure 147b shows how to calculate
approximately the delay relating it to the name of the iteration in the for loop.

(a) (b)

Figure 147

70
Section 29 : Array in C
Let’s understand why we need array. Let’s suppose it is needed to perform the average age of students in a class, there are 100 students
in a class. The classical approach needs to create 100 variables to store all the data. Each variable should be initialized at a different
value, this would be very slow and not efficient approach.

Figure 148

Figure 148 shows the difference between classical approach of the variables (left part) and the definition of array (right part in square
brackets) for the same purpose. The other two array definition on the right column are wrong.
An array in C is then a collection of data of the same datatype, the number in the square brackets indicates the number of items in the
array.
Let’s analyze an array example more in details.

uint8_t studentsAge[100];
In this case studentsAge is a base pointer or reference to 100 data items of type uint8_t.
studentsAge datatype is then uint8_t*. The datatype of the item stored is uint8_t. The size of an array in memory is very simple to
calculate.

Size memoryarray = numberdata items · (sizesingle data item )


In the previous example each item takes 1 byte of memory. This means that the whole array needs to occupy 100 bytes in memory.
It’s possible to use the command sizeof on the array in the following way.

sizeof (studentsAge) = 100


All the data of the array will be stored in contiguous memory locations in memory as it’s shown in Figure 149.

Figure 149

If the programmer try to use the the printf using %p to print the address for the array, the base address of the array will be printed
(the address of the first element of the array).

71
Read and write operation on a Array in C
A program will be written in order to better understand the concepts of :

• array inizialization

• array read and write

The program has to initialize an array of 10 elements to 0xf f and then prints the values of each element. Figure 150 shows 3
different types of initialization for an array type in C.

(a) (b)

(c)

Figure 150

Figure 150 shows 3 modalities of initialization of an array. Figure 150a shows an initialization element by element. It’s worth to
notice that array has 10 elements but just the value of 3 of them is defined. In this case the not defined elements will present a value
equal to 0. Figure 150b shows a case in which the dimension of the array is not directly fixed by programmer. The definition of the array
elements value is the same than in the previous case, the compiler will generate an array of 3 elements in this case. Compiler won’t give
any error because it will be able to define the number of array from the defined element values. Figure 150c shows a totally different
case in which the length of the array is not fixed anymore, it can vary in the execution of a program. This is a modality of initialization
allowed from C99 standard to go on. For previous standard the compiler will produce error in case of this initialization.
Let’s see how to write a new value to an array element. The simple procedure is to start from array base address and increment the
pointer in order to select the right array position in which we want to write. Dereferencing an array will allow us to write a value in that
memory location. This is shown in Figure 150a on the bottom before curly bracket, the expression after the curly bracket has the same
effect, both ways are correct but the second one is much practical from the programmer point of view.

(a) (b)

(c)

Figure 151

The number in the square brackets clearly defines the offset of the memory location from the base address pointer. This concept is
clarified in Figure 150b. Let’s see now how to read from an array. The example in Figure 150c shows how to print all the elements of
one array, a loop is necessary here (for loop is used in this case). To read just one element it’s enough to select the array element in the
square bracket in the printf command. .

72
Let’s see finally how to pass the content of an array to a function. Figure 151 clarify how to do that.

Figure 152

It is worth to notice that two informations are necessary to the function in order to print the array, the elements of the array and the
size of the array. When the main function recall the void function it passes to the last one a pointer, the base address of the array to be
more precise. The size has to be always passed as well. There are two implementation of the for loop to print the elements of the array.
Both are correct but the one in the small square on the right part of Figure 152 is a lot more common because it’s simpler to use.

Section 30 : Strings
A string is a collection of characters terminated by a null character. A null character is used to indicate end of string. For example a
name is a collection of character and can be represented as a string. It is possible use an array to store strings (name or textual message).
Differently than C++, Java, Python, the programming language C does not have any dedicate datatype to store string data. In C, array
are needed in order to store and manipulate string data.
Let’s see how to store a string. A string is then a array of character elements.

(a) (b)

Figure 153

Figure 153a shows a first example of a string definition and how it’s arranged in memory. An array of char datatype is defined, the
string in this case is made of 5 characters. The space in memory, on the other hand, will take 6 register. As it was already mentioned the
null character has to be inserted at the end of the string. The null character is indicated as \0. This operation has not to be done manually
by user, compiler adds automatically null character at the end of a string. Figure 153b shows another way to define a string (not so used
but correct as well), this is a wrong definition of a string. The string was defined element by element but actually the null character is
missed in the end. The compiler won’t add null character because user has to add it manually in this case.

(a) (b)

Figure 154

Figure 154a shows two ways to initialize a string, in the first case the programmer fixed the number of elements in the array. In the
second case, the length is not defined. In the first case, the compiler will assign the defined character to string and replace with null
character the rest of the memory location assigned. The second case regards a dynamic storage allocation, the compiler uses the number
of bytes to store the message + null character. Using sizeof command it’s possible to check the memory storage usage (6 bytes vs 10

73
bytes). The library function strlen returns the memory usage not considering the null characters, it’s possible to see that in both cases
this function returns 5 bytes as memory usage indeed.
Figure 154b remarks the difference between a character datatype and a string definition, the string is always selected by an array using
square brackets. In the case of single character datatype, no null character is added. Figure 154b shows how the compiler stores the two
types of information in memory.
Let’s introduce a new concept which is the string literal or string constant.

char ∗ msg1 = ”hello”;

char ∗ msg2[ ] = ”hello”;


The first above expression regards a char pointer. The compiler is going to store an address, the second expression is a string character
definition. The pointers are stored actually in a ROM memory while the strings are memorized in RAM. This implies that it’s possible to
modify string elements while it’s not possible to modify string literal or string constant considering they’re stored in ROM memory.

(a) (b)

(c)

Figure 155

Let’s analyze now the program in Figure 155a. Let’s focus first omn the msg1[] initialization. This is an array defined inside the
main function. It’s a local variable then, it is known that local variables are stored just during the run time of the program and then reset.
Figure 155b shows how the informations are stored in the memory. The big top bar indicates the RAM memory, the size is referred to
the ST discovery as usual. It can be seen that the RAM is divided in several sections, one is dedicated to the global variables allocation,
the stack stores local variables during run time, heap is dedicated to the dynamic memory allocation (which is not used in this course).
The stack will be tracked by SP or stack pointer. When variables are created SP is decremented, when local variables are erased (at the
end of the execution of the program) the SP is increased actually.
The msg1[] is then copied in the stack. The string written in msg1[] array is first stored in flash memory, then copied and pasted to
stack. There are two copies then of the assigned string (one in the flash memory and the other in the stack). The string assignment in
Figure 155b (msg1[] = ’k’;) will modify the copy in the stack section. It’s not possible to modify the flash memory because it’s writing
protected. If the array was defined as global data, the data would be passed from flash memory to the RAM section dedicated to global
variables.
Let’s now focus on the assignment performed on the pointer in the code in Figure 155a. In this case the memory allocation is shown in
Figure 155c. The main difference now is that the pointer pmsg2 is created in the stack part of RAM and it’s pointing to defined string
at line 16 in Figure 155a. When a new string value is assigned dereferencing the pointer as in Figure 155c, this command will have
no effect in the microcontroller while it can bring to the crash of the program if it’s run on our PC. The flash controller will block this
command makes it not effective in any case.
It’s worth to notice that the format specifier %s has to be used in order to print string content.

74
It’s important to see now how to allow an user to insert a string in our program.

(a) (b)

Figure 156

Let’s analyze the code in figure 156. Figure 156a shows an example of already seen scanf command. What change is the datatype
specifier (% s) and the fact that the name of the array is indicated directly (there is no need to use &) because this is a pointer and it’s
already an address. Observing the output program in Figure 156a it’s possible to see that it’s not the desired one actually. This happen
because the compiler accepts the string value until space is written by the user. The compiler stops to acquire the string then. It’s needed
in this case a particular case of scanf called scanset. The scanset command allow the programmer to select a character until compiler
has to continue to take string as input. This is performed adding square brackets (Figure 156b) and exponential symbol. The value in the
square brackets will be the last one to be take by compiler as string. If a value is character as c, the compiler will stop to take string on
the first c character inserted by user. In the case of Figure 156b \n is used in order compiler to store string until the user will press enter
(inserting a new line).

Section 31 : Pre-processor directives in C


It’s worth to start with some main basic about pre-processor directive in C:
• In C programming pre-processor directives are used to affect compile-time settings

• Pre-processor directives are also use to create macros used as a textual replacement for number and other things
• Pre-processor directives begin with "#" symbol
• Pre-processor directives are resolved or taken cared during the pre-processing stage of compilation
The pre-processor directives supported in C language are shown in Figure 157.

Figure 157

Macros in C
The main basic concepts to remember about macros in C are the following ones:

• Macros are written in C using # define pre-processor directives


• Macros are used for textual replacement in code, commonly used to define constants
• Syntax : # define <Identifier> <value>

• Example : # define MAX_RECORD 10

75
It’s worth to remark that there is always a space between the #define and the identifier and between the identifier and the value in a
macros definition. An example to show when a macro can be necessary is shown in Figure 158.

Figure 158

This is a program written previously in the course. It is know that the the minimum age is a constant. In the right part in Figure 158
the constant will be replaced with its value before to start the program. A constant that is used many time in the same program could
be change in the future. Using the standard approach we should change its value manually in the whole program. Using macros it’s
possible to change just the value in the macro definition to solve the problem.
The macros are not variables, they’re just identifier. This is just a textual replacement, there is not usage of memory storage needed
for macros. In embedded system programming, a lot of C macros are used to define pin numbers, pin values, crystal speed, peripheral
register addresses, memory addresses and for other configuration values. Figure 159a shows some typical example. In general capital
letters are chosen for macros name to allow to distinguish immediately between a macro and a variable.

(a) (b)

Figure 159

No semicolon is needed to end a macro definition. The term UL is unsigned long as value datatype. The macros can be used as
functions. Figure 159b shows an example of a function used like macro. There is a macro name and a value which is an expression. The
term PI_VALUE is another macro defined before in the example. The expression in C statement is actually equivalent to two operations
performed in pre-processor phase. The value of the variable results equal to the value of the expression defined in the Macro, in the
second step the constant assigned value are replaced in order to calculate the final value.
The function used like macro in Figure 159b is poorly written and dangerous. It’s not proper defined a function in this way. Let’s analyze
an example to understand the reason. Figure 160 shows the usage of the function macro defined in Figure 159b.

Figure 160

The problem that it’s immediately noticed is that the function produces two different values inserting the same input equal to 2 in
two different ways (second output is shown in the square on the bottom right part of figure). This means that the function is not properly
defined.
In order to find the reason for that it’s important to remember that the macro is a replacement, let’s replace the expression of the function
with the input value then.

AREA_OF _CIRCLE(r) = 3.1415 ∗ 1 + 1 ∗ 1 + 1

76
Observing the above expression, it’s known that the result will be evaluated according to the previour rules on the priority and order
of operations between operands. This actually changes the result indeed.
The solution to defined in proper way is always use generously brackets in order to avoid misunterstandings. The above expression then
should be written in this way.

AREA_OF _CIRCLE(r) = ((P I_V ALU E) ∗ (r) ∗ (r))


Use always parentheses for each operand then in order to avoid result mismatches depending on how input is inserted.
It’s worth to repeat and resume all the rules in order to define properly macros in C.

1. Use meaningful macro names


2. It’s recommended that you use UPPER case letters for macro names to distinguish them from variables
3. Remember, macro’s names are not variables. They are label or identifiers, and they do not consume any code space or ram space
during compile time or run time of the program.

4. Make sure that parentheses surround the macro value


5. While using function like macro or when you are using macros along with any operators, always surround the operands with
parentheses.

Conditional compilation directives


Several conditional compilation directives are available in C, some types are listed below.

1. #if 4. #else

2. #ifdef 5. #undef
3. #endif 6. #ifndef

These directives help you to include or exclude individual blocks based on various conditions set in the program. Figure 161a shows
the syntax and an example of one of the directive listed before.

(a) (b)

Figure 161

It’s worth to notice that in the #if directive an integer or a number has to be inserted. It’s always necessary to close the directive with
a #endif directive. This command is used for each of the conditional compilation directives available in C.
The directive checks in which case the constant expression is zero or non zero value. If constant is 0, the code block won’t be included
in the code compilation (the compiler will ignore this piece of code). If constant is non zero, the code block will be included for the code
compilation.
Don’t confuse this directive with the if statement in the decisional command seen previously in the part of the course. This is a pre-
processor stage command. In the end, this piece of code won’t be included in the executable file generated by compilation. Figure 161b
shows a code example of how this conditional directive can be used. Considering that a 0 is inserted after #if statement the piece of
code will be ignored. If the programmer needs to include this piece of code it’s enough to change the constant to non zero value(1 for
example). This conditional directive can be used in nested way as well if the programmer wants to exclude just a section of the code
already include in the #if directive.

77
Let’s pass to another directive in the list shown in Figure 162a. This directive is very similar to the previous one but it gives chance
to include one block between two available due to the constant chosen after #if command. If the programmer will write 0 as constant,
the first piece of code will be ignored and the other will be included (the one after #else). If constant will be 1 the first piece of code will
be included. A clear example is shown in Figure 162b, first piece of code is ignored because constant is 0 in this case. Changing the
value of the constant in non-zero value will allow to include the second block after the #else statement.

(a) (b)

Figure 162

Let’s focus now on the directive #ifdef. Figure 163a shows the syntax and a first example.This directive works on the definition of a
macro. The evaluation will be perfomed on the base of an identifier and not a constant as in previous cases. A space has to be inserted
between #ifdef and the identifier. The directive checks if a macro is defined in the program by name. If the macro is defined the piece of
code will be included in the build compilation process.
A program is shown in Figure 162b, the code is not included because no macro with that name was defined in the pre-processor stage.
It’s possible to see that this piece of code is included, as expected, if a macro with that name is defined before function area (Figure
163c) .

(a) (b)

(c)

Figure 163

Another directive is the #ifndef directive. It works exactly in the opposite way compared the the previous one. Figure 164 shows its
syntax.

Figure 164

78
This directive checks if the macro in identifier is not defined. If the macro is the defined the code is included in build process.
The directives #ifdef and #ifndef can be used adding a else statement. This will allow to choose one between the two piece of code
depending if macro is defined or not.
The last part of this section will be focused on the defined operator used together with the #if. The defined operator is used when you
want to check definitions of multiple macros using single #if, #ifdef or #ifndef directives. You can also use C logical operators such as
AND,NOT, OR with defined operators. Figure 165 shows how it’s possible to use the above mentioned operators.

Figure 165

The statement #ifdef takes in consideration two macros in the condition. The piece of code will be included just if both macros
are defined in the pre-processing stage (considering the AND operator). The condition can accept OR or NOT operators as well as
already mentioned. Another very useful pre-processing possibility is the one to create warning or errors due to particolar condition
using operators as in Figure 165. Figure 166a shows a particular condition to show an error to the user and block the compilation as
well. The error in Figure 166a occurs just if none of the two macros in parentheses are defined. Figure 166b shows the same condition,
the main difference is that the programmer defined a warning in this case. The message then will be always communicated to the user
but the compilation will proceed anyway.

(a) (b)

Figure 166

79

You might also like