Basic Programming Knowledge
Basic Programming Knowledge
Introduction on Embedded C
Embedded C programming is crucial for developing firmware in various systems such as IoT
devices and industrial controllers. As we progress through 2024, it’s important to refine our
approach to mastering this skill. This guide explores core concepts, modern practices, and
advanced topics relevant to embedded C programming this year.
• Growth of the IoT: The proliferation of connected devices necessitates compact, low-
power firmware, making embedded C an ideal choice for resource-constrained IoT nodes.
• Focus on Security: As embedded systems become more interconnected, security
vulnerabilities pose a significant threat. Understanding secure coding practices in
embedded C is paramount.
• Hardware Advancements: Modern microcontrollers offer a plethora of new features
like hardware accelerators and security modules. Effective embedded C code leverages
these features for improved performance and security.
Core Concepts
A solid foundation in C programming fundamentals is crucial for mastering embedded C. This
section revisits essential concepts with an embedded systems perspective:
• Data Types: Grasping the nuances of data types like char, int, long, and structures is vital
for memory management and efficient data manipulation.
• Operators: Operators like bitwise operators (&, |, ^), arithmetic operators (+, -, *, /), and
comparison operators (==, !=, <, >) are fundamental for interacting with hardware
registers and performing calculations.
• Control Flow: Control flow statements like if, else, for, while, and switch enable
decision-making and program execution flow control, essential for implementing control
algorithms.
• Functions: Functions promote code modularity, reusability, and maintainability.
Understanding function prototypes, arguments, return values, and recursion is crucial.
• Pointers: Pointers provide a powerful mechanism for memory management and low-
level hardware interaction. Mastering pointer arithmetic and avoiding dangling pointers is
essential for safe and efficient code.
Interfacing with the physical world is a core function of embedded systems. This section covers
various I/O mechanisms and their C programming aspects:
Embedded systems often respond to external events. This section explores interrupts and real-
time concepts:
• Debuggers: Debuggers allow for step-by-step code execution, variable inspection, and
breakpoint setting, crucial for troubleshooting and code optimization.
• Emulators and Simulators: Emulators mimic the behavior of a microcontroller on a
host computer, while simulators model the microcontroller’s instruction set. These tools
are valuable for pre-silicon development and testing.
• Integrated Development Environments (IDEs): IDEs combine editing, compiling,
debugging, and project management functionalities in a single interface, streamlining the
development workflow. Popular embedded C IDEs include Keil MDK-ARM, IAR
Embedded Workbench, and Eclipse with CDT plugin.
Advanced Topics
This section explores advanced concepts for those seeking to delve deeper:
Introduction
Embedded C Course is a set of language extensions for the C programming language by the C
Standards Committee to statement commonality issues that exist between C extensions for
different embedded systems. It is used to develop microcontroller programming applications.
It includes features not available in standard C like fixed-point arithmetic, named address spaces,
and necessary I/O hardware addressing. Cell phones, MP3 players are some examples of
embedded systems in which embedded C is used to program and control these devices.
What is a program in C?
A computer program written in C is a human readable and ordered set of instructions that a
computer executes. It aims to provide a solution to a specific computing problem and tell the
computer to perform a certain task with a sequence of instructions that it needs to follow.
Essentially all programs are just plain text files stored on your computer’s hard drive that use a
special syntax which is defined by the programming language you're using.
Each language has its own rules that dictate what you can write and what's considered valid, and
what is not.
A program has keywords, which are specific words that are reserved and are part of the
language. It also has literal pieces of data like strings and numbers. And it has words that follow
the language’s rules, which we define and introduce to the language that don’t already exist (like
variables or methods).
What is a compiler?
Programs are written by us and for us. They are meant to be understood by humans.
When we write programs in human readable form, we can understand them – but the computer
may not be able to. Computers don’t directly understand programming languages, they only
understand binary. So programs need to be translated into this other form so the computer can
actually understand our program's instructions.
Programs in high level languages can be either compiled or interpreted. They use special pieces
of software called compilers and interpreters, respectively.
Both compilers and interpreters are programs, but they're far more complex ones, and they act as
translators. They take a program that's written in a human readable form and turn it into
something that computers can make sense of. And they make it possible to run and execute
programs on different computer systems.
Compiled programs are first converted into machine-readable form which means they are
translated into machine code before they run. Machine code is a numerical language – binary
instructions composed of sequences of 0s and 1s.
This compilation produces an executable program, that is a file containing the code in the
machine language that the CPU (Central Processing Unit) will be able to read, understand, and
execute directly.
After this, the program can run and the computer does what the program tells it to do. Compiled
programs have a stronger correspondence with the underlying hardware and can more easily
manipulate the computer's CPU and memory.
Interpreted programs, on the other hand, are not directly executed by the machine nor do they
need to be translated into a machine language program. Instead, they use an interpreter that
automatically and directly translates and executes each statement and instruction in the code line
by line during run time.
C is a compiled programming language. This means that it uses a compiler to analyse the source
code written in C and then turns it into a binary file that the computer's hardware can directly
execute. This will be specific for each particular machine.
Preprocessor Directives
Preprocessor directives are not normal code statements. They are lines of code that begin with
the character "#" and appear in Embedded C programming before the main function. At runtime,
the compiler looks for preprocessor directives within the code and resolves them completely
before resolving any of the functions within the code itself. Some preprocessor directives can
skip part of the main code based on certain conditions, while others may ask the preprocessor to
replace the directive with information from a separate file before executing the main code or to
behave differently based on the hardware resources available. Many types of preprocessor
directives are available in the Embedded C language.
Global declarations happen before the main function of the source code. Engineers can declare
global variables that may be called by the main program or any additional functions or sub-
programs within the code. Engineers may also define functions here that will be accessible
anywhere in the code.
Main Program
The main part of the program begins with main(). If the main function is expected to return an
integer value, we would write int main(). If no return is expected, convention dictates that we
should write void main(void).
• Declaration of local variables - Unlike global variables, these ones can only be called
by the function in which they are declared.
• Initializing variables/devices - A portion of code that includes instructions for
initializating variables, I/O ports, devices, function registers, and anything else needed for
the program to execute
• Program body - Includes the functions, structures, and operations needed to do
something useful with our embedded system
Subprograms
An embedded C program file is not limited to a single function. Beyond the main() function,
programmers can define additional functions that will execute following the main function when
the code is compiled.
Variables are containers for storing data values, like numbers and characters.
In C, there are different types of variables (defined with different keywords), for example:
• int - stores integers (whole numbers), without decimals, such as 123 or -123
• float - stores floating point numbers, with decimals, such as 19.99 or -19.99
• char - stores single characters, such as 'a' or 'B'. Char values are surrounded by single quotes
Syntax
Where type is one of C types (such as int), and variableName is the name of the variable (such
as x or myName). The equal sign is used to assign a value to the variable.
So, to create a variable that should store a number, look at the following example:
Example
Create a variable called myNum of type int and assign the value 15 to it:
You can also declare a variable without assigning the value, and assign the value later:
Example
// Declare a variable
int myNum;
Output Variables
Example
Example
// Create variables
int myNum = 15; // Integer (whole number)
float myFloatNum = 5.99; // Floating point number
char myLetter = 'D'; // Character
// Print variables
Serial.print("%d\n", myNum);
Serial.print("%f\n", myFloatNum);
Serial.print("%c\n", myLetter);
To combine both text and a variable, separate them with a comma inside the Serial.print()
function:
Example
To print different types in a single Serial.print() function, you can use the following:
Example
Note: If you assign a new value to an existing variable, it will overwrite the previous value:
Example
Example
Example
Example
int x = 5;
int y = 6;
int sum = x + y;
Serial.println(sum);
To declare more than one variable of the same type, use a comma-separated list:
Example
int x = 5, y = 6, z = 50;
You can also assign the same value to multiple variables of the same type:
Example
int x, y, z;
x = y = z = 50;
C Variable Names
Identifiers can be short names (like x and y) or more descriptive names (age, sum, totalVolume).
// Good
int minutesPerHour = 60;
Real-Life Example
Often in our examples, we simplify variable names to match their data type (myInt or myNum
for int types, myChar for char types etc). This is done to avoid confusion.
However, if you want a real-life example on how variables can be used, take a look at the
following, where we have made a program that stores different data of a college student:
Example
// Student data
int studentID = 15;
int studentAge = 23;
float studentFee = 75.25;
char studentGrade = 'B';
// Print variables
Serial.print(“ Student ID: “);
Serial.println(studentID);
Serial.print(“ Student Age: “);
Serial.println(studentAge);
The data type specifies the size and type of information the variable will store.
You have probably already noticed that if you print a floating-point number, the output will show
many digits after the decimal point:
Example
Constants
If you don't want others (or yourself) to change existing variable values, you can use the const
keyword.
This will declare the variable as "constant", which means unchangeable and read-only:
Example
const int myNum = 15; // myNum will always be 15
myNum = 10; // error: assignment of read-only variable 'myNum'
You should always declare the variable as constant when you have values that are unlikely to
change:
Example
const int minutesPerHour = 60;
const float PI = 3.14;
Notes On Constants
Example
Like this:
const int minutesPerHour = 60;
Another thing about constant variables, is that it is considered good practice to declare them with
uppercase. It is not required, but useful for code readability and common for C programmers:
Example
const int BIRTHYEAR = 1980;
➢ Operators
Operators are used to perform operations on variables and values.
In the example below, we use the + operator to add together two values:
Example
Although the + operator is often used to add together two values, like in the example above, it
can also be used to add together a variable and a value, or a variable and another variable:
Example
• Arithmetic operators
• Assignment operators
• Comparison operators
• Logical operators
• Bitwise operators
Arithmetic Operators
Assignment Operators
Assignment operators are used to assign values to variables.
In the example below, we use the assignment operator (=) to assign the value 10 to a variable
called x:
Example
int x = 10;
Example
int x = 10;
x += 5;
A list of all assignment operators:
= x=5 x=5
+= x += 3 x=x+3
-= x -= 3 x=x-3
*= x *= 3 x=x*3
/= x /= 3 x=x/3
%= x %= 3 x=x%3
|= x |= 3 x=x|3
^= x ^= 3 x=x^3
Comparison Operators
Comparison operators are used to compare two values (or variables). This is important in
programming, because it helps us to find answers and make decisions.
The return value of a comparison is either 1 or 0, which means true (1) or false (0). These values
are known as Boolean values, and you will learn more about them in the Booleans and If..Else
chapter.
In the following example, we use the greater than operator (>) to find out if 5 is greater than 3:
Example
int x = 5;
int y = 3;
Serial.print("%d", x > y); // returns 1 (true) because 5 is greater than 3
A list of all comparison operators:
== Equal to x == y
!= Not equal x != y
Greater than
>= x >= y
or equal to
Less than or
<= x <= y
equal to
Logical Operators
You can also test for true or false values with logical operators.
Logical operators are used to determine the logic between variables or values:
! Logical not Reverse the result, returns false if the result is true !(x < 5 && x < 10)
Sizeof Operator
The memory size (in bytes) of a data type or a variable can be found with the sizeof operator:
Example
int myInt;
float myFloat;
double myDouble;
char myChar;
Serial.print(sizeof(myInt));
Serial.print("%lu\n", sizeof(myFloat));
Serial.print(sizeof(myDouble));
Serial.print(sizeof(myChar));
➢ Booleans
Very often, in programming, you will need a data type that can only have one of two values, like:
• YES / NO
• ON / OFF
• TRUE / FALSE
Boolean Variables
In C, the bool type is not a built-in data type, like int or char.
It was introduced in C99, and you must import the following header file to use it:
#include <stdbool.h>
A boolean variable is declared with the bool keyword and can only take the values true or
false:
Before trying to print the boolean variables, you should know that boolean values are returned as
integers:
Example
However, it is more common to return a boolean value by comparing values and variables.
Comparing values are useful in programming, because it helps us to find answers and make
decisions.
For example, you can use a comparison operator, such as the greater than (>) operator, to
compare two values:
Example
From the example above, you can see that the return value is a boolean value (1).
Example
int x = 10;
int y = 9;
Serial.print("%d", x > y);
In the example below, we use the equal to (==) operator to compare different values:
Example
Example
Remember to include the <stdbool.h> header file when working with bool variables.
In the example below, we use the >= comparison operator to find out if the age (25) is greater
than OR equal to the voting age limit, which is set to 18:
Example
Serial.print("%d", myAge >= votingAge); // Returns 1 (true), meaning 25 year olds are allowed to vote!
Cool, right? An even better approach (since we are on a roll now), would be to wrap the code
above in an if...else statement, so we can perform different actions depending on the result:
Example
Output "Old enough to vote!" if myAge is greater than or equal to 18. Otherwise output "Not
old enough to vote.":
You have already learned that C supports the usual logical conditions from mathematics:
You can use these conditions to perform different actions for different decisions.
The if Statement
Syntax
if (condition) {
// block of code to be executed if the condition is true
}
Note that if is in lowercase letters. Uppercase letters (If or IF) will generate an error.
In the example below, we test two values to find out if 20 is greater than 18. If the condition is
true, print some text:
Example
if (20 > 18) {
Serial.print("20 is greater than 18");
}
Example
int x = 20;
int y = 18;
if (x > y) {
Serial.print("x is greater than y");
}
Example explained
In the example above we use two variables, x and y, to test whether x is greater than y (using the
> operator). As x is 20, and y is 18, and we know that 20 is greater than 18, we print to the screen
that "x is greater than y".
Use the else statement to specify a block of code to be executed if the condition is false.
Syntax
if (condition) {
// block of code to be executed if the condition is true
} else {
// block of code to be executed if the condition is false
}
Example
In the example above, time (20) is greater than 18, so the condition is false. Because of this, we
move on to the else condition and print to the screen "Good evening". If the time was less than
18, the program would print "Good day".
Use the else if statement to specify a new condition if the first condition is false.
Syntax
if (condition1) {
// block of code to be executed if condition1 is true
} else if (condition2) {
// block of code to be executed if the condition1 is false and condition2 is true
} else {
// block of code to be executed if the condition1 is false and condition2 is false
}
Example
Example explained
In the example above, time (22) is greater than 10, so the first condition is false. The next
condition, in the else if statement, is also false, so we move on to the else condition since
condition1 and condition2 is both false - and print to the screen "Good evening".
However, if the time was 14, our program would print "Good day."
Another Example
This example shows how you can use if..else to find out if a number is positive or negative:
Example
if (myNum > 0) {
Serial.print("The value is a positive number.");
} else if (myNum < 0) {
Serial.print("The value is a negative number.");
} else {
Serial.print("The value is 0.");
}
Switch Statement
Instead of writing many if..else statements, you can use the switch statement.
Syntax
switch(expression) {
case x:
// code block
break;
case y:
// code block
break;
default:
// code block
}
The example below uses the weekday number to calculate the weekday name:
Example
int day = 4;
switch (day) {
case 1:
Serial.print("Monday");
break;
case 2:
Serial.print("Tuesday");
break;
case 3:
Serial.print("Wednesday");
break;
case 4:
Serial.print("Thursday");
break;
case 5:
Serial.print("Friday");
break;
case 6:
Serial.print("Saturday");
break;
case 7:
Serial.print("Sunday");
break;
}
This will stop the execution of more code and case testing inside the block.
When a match is found, and the job is done, it's time for a break. There is no need for more
testing.
A break can save a lot of execution time because it "ignores" the execution of all the rest of the
code in the switch block.
The default keyword specifies some code to run if there is no case match:
Example
int day = 4;
switch (day) {
case 6:
Serial.print("Today is Saturday");
break;
case 7:
Serial.print("Today is Sunday");
break;
default:
Serial.print("Looking forward to the Weekend");
}
Note: The default keyword must be used as the last statement in the switch, and it does not need
a break.
C While Loop
Loops
Loops are handy because they save time, reduce errors, and they make code more readable.
While Loop
The while loop loops through a block of code as long as a specified condition is true:
Syntax
while (condition) {
// code block to be executed
}
In the example below, the code in the loop will run, over and over again, as long as a variable (i)
is less than 5:
Example
int i = 0;
while (i < 5) {
Serial.print("%d\n", i);
i++;
}
Note: Do not forget to increase the variable used in the condition (i++), otherwise the loop will
never end!
The do/while loop is a variant of the while loop. This loop will execute the code block once,
before checking if the condition is true, then it will repeat the loop as long as the condition is
true.
Syntax
do {
// code block to be executed
}
while (condition);
The example below uses a do/while loop. The loop will always be executed at least once, even
if the condition is false, because the code block is executed before the condition is tested:
Example
int i = 0;
do {
Serial.print( i);
i++;
}
while (i < 5);
Do not forget to increase the variable used in the condition, otherwise the loop will never end!
C For Loop
For Loop
When you know exactly how many times you want to loop through a block of code, use the for
loop instead of a while loop:
Syntax
Statement 1 is executed (one time) before the execution of the code block.
Statement 3 is executed (every time) after the code block has been executed.
Example
int i;
Example explained
Statement 3 increases a value (i++) each time the code block in the loop has been executed.
Another Example
This example will only print even values between 0 and 10:
Example
Nested Loops
It is also possible to place a loop inside another loop. This is called a nested loop.
The "inner loop" will be executed one time for each iteration of the "outer loop":
Example
int i, j;
// Outer loop
for (i = 1; i <= 2; ++i) {
Serial.print( i); // Executes 2 times
// Inner loop
for (j = 1; j <= 3; ++j) {
Serial.print(" Inner: %d\n", j); // Executes 6 times (2 * 3)
}
}
C Arrays
Arrays
Arrays are used to store multiple values in a single variable, instead of declaring separate
variables for each value.
To create an array, define the data type (like int) and specify the name of the array followed by
square brackets [].
Array indexes start with 0: [0] is the first element. [1] is the second element, etc.
This statement accesses the value of the first element [0] in myNumbers:
Example
// Outputs 25
Example
myNumbers[0] = 33;
Example
Another common way to create arrays, is to specify the size of the array, and add elements later:
Example
// Add elements
myNumbers[0] = 25;
myNumbers[1] = 50;
myNumbers[2] = 75;
myNumbers[3] = 100;
Using this method, you should know the size of the array, in order for the program to store
enough memory.
You are not able to change the size of the array after creation.
C Strings
Strings
Unlike many other programming languages, C does not have a String type to easily create string
variables. Instead, you must use the char type and create an array of characters to make a string
in C:
To output the string, you can use the printf() function together with the format specifier %s to
tell C that we are now working with strings:
Example
Access Strings
Since strings are actually arrays in C, you can access a string by referring to its index number
inside square brackets [].
Example
Note that we have to use the %c format specifier to print a single character.
Modify Strings
To change the value of a specific character in a string, refer to the index number, and use single
quotes:
Example
C Functions
Predefined Functions
So it turns out you already know what a function is. You have been using it the whole time while
studying this tutorial!
For example, main() is a function, which is used to execute code, and printf() is a function;
used to output/print text to the screen:
Example
int main() {
printf("Hello World!");
return 0;
}
Create a Function
To create (often referred to as declare) your own function, specify the name of the function,
followed by parentheses () and curly brackets {}:
Syntax
void myFunction() {
// code to be executed
}
Example Explained
Call a Function
Declared functions are not executed immediately. They are "saved for later use", and will be
executed when they are called.
To call a function, write the function's name followed by two parentheses () and a semicolon ;
In the following example, myFunction() is used to print a text (the action), when it is called:
Example
// Create a function
void myFunction() {
printf("I just got executed!");
}
int main() {
myFunction(); // call the function
return 0;
}
Example
void myFunction() {
printf("I just got executed!");
}
int main() {
myFunction();
myFunction();
myFunction();
return 0;
}
You just learned from the previous chapters that you can create and call a function in the
following way:
Example
// Create a function
void myFunction() {
printf("I just got executed!");
}
int main() {
myFunction(); // call the function
return 0;
}
• Declaration: the function's name, return type, and parameters (if any)
• Definition: the body of the function (code to be executed)
For code optimization, it is recommended to separate the declaration and the definition of the
function.
You will often see C programs that have function declaration above main(), and function
definition below main(). This will make the code better organized and easier to read:
Example
// Function declaration
void myFunction();
// Function definition
void myFunction() {
printf("I just got executed!");
}
Another Example
If we use the example from the previous chapter regarding function parameters and return
values:
Example
int main() {
int result = myFunction(5, 3);
printf("Result is = %d", result);
return 0;
}
// Outputs 8 (5 + 3)
Example
// Function declaration
int myFunction(int, int);
// Function definition
int myFunction(int x, int y) {
return x + y;
}
C Comments
Comments in C
Comments can be used to explain code, and to make it more readable. It can also be used to
prevent execution when testing alternative code.
Single-line Comments
Any text between // and the end of the line is ignored by the compiler (will not be executed).
Example
// This is a comment
printf("Hello World!");
Example
C Multi-line Comments
Example
void setup() {
void loop() {
Comments:
/* Multi-line
comment */
Modifiers:
1.1.3 Operators
Arithmetic Operators:
int sum = 5 + 3; // 8
int product = 5 * 3; // 15
Relational Operators:
Logical Operators:
Assignment Operators:
Example:
void setup() {
}
void loop() {
} else {
Example:
void setup() {
void loop() {
case 0:
break;
case 1:
break;
default:
1.2.3 Loops
for Loop:
Example:
void setup() {
void loop() {
}
delay(2000); // Wait for 2 seconds before repeating
while Loop:
Example:
void setup()
void loop() {
int count = 0;
count++;
do-while Loop:
Example:
void setup() {
void loop() {
int count = 0;
do {
count++;
1.3 Functions
1.3.1 Declaration
Syntax:
return_type function_name(parameters);
Example: int calculateSum(int a, int b);
1.3.2 Definition
Syntax:
return_type function_name(parameters) {
// Code
return value;
}
Example:
1.3.3 Usage
Example:
void setup() {
void loop() {
}
int calculateSum(int a, int b) {
return a + b;
1.4 Pointers
1.4.1 Basics
Definition:
Syntax:
type *pointer_name;
Example:
1.4.2 Usage
Dereferencing a Pointer:
Example:
void setup() {
Serial.begin(9600);
void loop() {
// Nothing to do here
Pointer Arithmetic:
Example:
void setup() {
Serial.begin(9600);
int *p = arr;
void loop() {
// Nothing to do here
}
Passing Pointers to Functions:
Syntax:
Example:
(*p)++;
void setup() {
Serial.begin(9600);
void loop() {
// Nothing to do here
This detailed context integrates the basic concepts of C programming with practical
Arduino examples, illustrating how these concepts apply in an embedded environment.
………………………………………………………………………………………………