Top Banner
Prepared by: Mohamed AbdAllah Embedded C-Programming Lecture 2 1
95

Embedded C - Lecture 2

Apr 21, 2017

Download

Engineering

Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Embedded C - Lecture 2

Prepared by: Mohamed AbdAllah

Embedded C-Programming

Lecture 2

1

Page 2: Embedded C - Lecture 2

Agenda

Preprocessor directives (continue).

Data types (Primitive, derived, user defined).

Qualifiers (Size, Sign and volatile modifiers and Storage classes).

Scope and lifetime.

Makefile introduction.

Task 2.

2

Page 3: Embedded C - Lecture 2

Agenda

3

Page 4: Embedded C - Lecture 2

Preprocessor directives (continue)

4

Page 5: Embedded C - Lecture 2

Examples on preprocessor directives (continue):

• Function-like Macro:

#define MAX(a,b) ((a)>(b)) ? (a):(b))

Define macro to be used like a function call, so typing:

int maximum_num = MAX(2,7);

will be replaced with:

int maximum_num = ((2)>(7)) ? (2):(7));

Which will make maximum_num value be 7

5

Preprocessor directives (continue)

Page 6: Embedded C - Lecture 2

Examples on preprocessor directives (continue):

#define OUTPUT 1

#define LOGIC_HIGH 1

#define LED_INIT(PORT,PIN)\

portInit(PORT,PIN,OUTPUT);\

portState(PORT,PIN,LOGIC_HIGH)

Define macro to be used like a function call, so typing:

LED_INIT(‘A’,3);

will be the same as typing:

portInit(‘A’,3,OUTPUT);

portState(‘A’,3,LOGIC_HIGH);

Note: Function-like Macro is like normal macro, it is just text replacement.

6

Preprocessor directives (continue)

Page 7: Embedded C - Lecture 2

Pros and Cons of using Function-like Macro

• Function-like macro usage is just copy and paste, so no function call actually occurs which makes it faster than function call.

• It is more difficult to debug in function-like macro code than normal function code.

• Macros are error-prone because they rely on textual substitution:

#define square(a) a*a

/* works fine when used with an integer */

square(5); /* --> 5*5 --> 25 */

/* but does very strange things when used with expressions */

square(1+2); /* --> 1+2*1+2 --> 1+2+2 --> 5 */

square(x++); /* --> x++*x++ --> increments x twice */

7

Preprocessor directives (continue)

Page 8: Embedded C - Lecture 2

Pros and Cons of using Function-like Macro

Using parentheses around all your variables inside macro will help in first problem but not the second:

#define square(a) ((a)*(a))

square(1+2); /* --> ((1+2)*(1+2)) --> 3*3 --> 9 */

square(x++);/*((x++)*(x++))-->still increments x twice*/

• Macros do not perform type-checking:

#define MAX(A,B) (((A)>(B)) ? (A) : (B))

Then doing the following will not produce any error or warning:

printf("%d\n", MAX("abc","def"));

But if the same was done with usal functions then the compiler will generate warning during compilation.

8

Preprocessor directives (continue)

Page 9: Embedded C - Lecture 2

Examples on preprocessor directives (continue):

• Stringification:

#define LED_INIT(PORT,PIN)\

printf("Initializing PORT: "#PORT" \n");\ portInit(PORT,PIN,OUTPUT);\

portState(PORT,PIN,LOGIC_HIGH)

When we want to convert a macro argument into a string constant.

So typing:

LED_INIT(‘A’,3);

will be the same as typing:

printf("Initializing PORT: A \n");

portInit(‘A’,3,OUTPUT);

portState(‘A’,3,LOGIC_HIGH);

9

Preprocessor directives (continue)

Page 10: Embedded C - Lecture 2

Examples on preprocessor directives (continue):

• Concatenation:

It is used to merge two tokens into one while expanding macros. This is called token pasting or token concatenation.

Suppose you have a function with 3 different implementations:

portInit_implementation1(void);

portInit_implementation2(void);

portInit_implementation3(void);

Then you can define:

#define PORT_INIT(NUM)\

portInit_implementation ## NUM ()

Then to choose which function of the 3 to call using only PORT_INIT:

PORT_INIT(2);

it is the same as typing:

portInit_implementation2();

10

Preprocessor directives (continue)

Page 11: Embedded C - Lecture 2

Data types

11

Page 12: Embedded C - Lecture 2

12

Data types

Page 13: Embedded C - Lecture 2

Primitive Data Types

• The primitive data types in c language are the inbuilt data types provided by the c language itself. Thus, all c compilers provide support for these data types.

• The following primitive data types in c are available:

Integer Data Type, int: Integer data type is used to declare a variable that can store numbers without a decimal. The keyword used to declare a variable of integer type is “int”. Thus, to declare integer data type following syntax should be followed:

int variable_name;

Float data Type, float: Float data type declares a variable that can store numbers containing a decimal number:

float variable_name;

13

Data types

Page 14: Embedded C - Lecture 2

Primitive Data Types

Double Data Type, double: Double data type also declares variable that can store floating point numbers but gives precision double than that provided by float data type. Thus, double data type are also referred to as double precision data type:

double variable_name;

Character Data Type, char: Character data type declares a variable that can store a character constant. Thus, the variables declared as char data type can only store one single character:

char variable_name;

Void Data Type, void: Unlike other primitive data types in c, void data type does not create any variable but returns an empty set of values. Thus, we can say that it stores null:

void myFunc(); /* Function returns void data type */

14

Data types

Page 15: Embedded C - Lecture 2

Derived Data Types

• A derived data type is a complex classification of a new data type that is made up of simpler primitive data type. Derived data types have advanced properties and uses far beyond those of the basic primitive data types that operate as their essential building blocks.

• The following are examples for derived data types:

Array: a kind of data structure that can store a fixed-size sequential collection of elements of the same type. So instead of typing:

int var1, var2, var3, var4;

We can simply make them in array of 4 integers:

int Arr[4]; /* Uninitialized */

OR

int Arr[4] = {3,2,4,6}; /* Initialized */

Array consists of contiguous memory locations. The lowest index Arr[0] corresponds to the first element and the highest index Arr[3] to the last element.

15

Data types

Page 16: Embedded C - Lecture 2

Derived Data Types (Array continue)

Array can be given value at declaration (Initialized array) or can have no initial values at declaration.

Array should be fixed size, and size should equal number of initialization elements:

int Arr[4]; /* Valid*/

int Arr[] = {3,2,4,6}; /* Valid, compiler will put size = 4 as it is initialized */

int Arr[]; /* Invalid, compiler error */

int Arr[2] = {5,6,7,8}; /* Invalid, compiler error */

Accessing array is through array name and element index:

Arr[2] = 10; /* Change third element */

Exceeding array limits:

int Arr[10]; /* Declare array of 10 integers*/

Arr[15] = 10; /* Will not produce compilation error, but at runtime it may cause a lot of problems (Data corruption, program termination, …) */

16

Data types

Page 17: Embedded C - Lecture 2

Derived Data Types (Array continue)

String doesn’t exist as a primitive data type in C language, but arrays can be used to represent string in an array of characters:

char Arr[6] = ‚Hello‛;

Note that array size is number of string characters + 1, this extra character is used to store the NULL ‘\0’ terminator which marks the end of string, so the following will lead to same result:

char Arr[6] = {‘H’,’e’,’l’,’l’,’o’,’\0’};

This NULL terminator is used to know where string ends, for example when required to print the string:

printf(‚%s‛, Arr) /* Will print Hello*/

Or by doing:

int i = 0;

while(Arr[i] != ‘\0’)

printf(‚%c‛, Arr[i++]); /* Will print Hello*/

17

Data types

Page 18: Embedded C - Lecture 2

Derived Data Types (Array continue)

Till now this is called 1-D (1 dimensional) array.

In memory it looks like that:

18

Data types

Arr[0]

Arr[1]

Arr[2]

Arr[3]

Page 19: Embedded C - Lecture 2

Derived Data Types (Array continue)

C language also provides a multi-dimensional array, for example we can create a 2-D array:

int Arr[3][4];

Which can be considered as a table with 3 rows and 4 columns:

19

Data types

column 0 column 1 column 2 column 3

row 0 Arr[0][0] Arr[0][1] Arr[0][2] Arr[0][3]

row 1 Arr[1][0] Arr[1][1] Arr[1][2] Arr[1][3]

row 2 Arr[2][0] Arr[2][1] Arr[2][2] Arr[2][3]

Generally we can any number of dimensions:

int Arr[size1][size2]………..[sizen];

Page 20: Embedded C - Lecture 2

Derived Data Types (Array continue)

2-D array initialization:

int Arr[3][4] = {

{1,22,13,40}, /* row 0 */

{7,4,45,7}, /* row 1 */

{23,34,83,47} /* row 2 */

};

It can be also initialized like that, but this is not recommended for readability:

int Arr[3][4] = {1,22,13,40,7,4,45,7,23,34,83,47};

20

Data types

Page 21: Embedded C - Lecture 2

Derived Data Types

Function: A function type describes a function with specified return type. A function type is characterized by its return type and the number and types of its parameters.

A function type is said to be derived from its return type, and if its return type is int , the function type is sometimes called function returning int.

/* Function that returns integer, and takes 2 parameters

first is integer and second is character*/

int myFunc(int x, char y);

This is called function declaration, or function signature.

21

Data types

Page 22: Embedded C - Lecture 2

Derived Data Types

Pointers: A pointer is a variable whose value is the address of another variable, i.e., direct address of the memory location:

int Var1 = 10; /* Var1 is integer contains value 10 */

int *ptr = &Var1; /* ptr is pointer to integer, its value is the address of Var1 in memory*/

22

Data types

Memory

Var1 = 10 0xFE00

Ptr = 0xFE00 0xFF04

Page 23: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

We can make the pointer points to another variable during runtime:

int Var1 = 10, Var2 = 30;

int *ptr = NULL; /* NULL Pointer points to nothing */

ptr = &Var1; /* Pointer now holds Var1 address */

*ptr = 20; /* Change Var1 value through pointer */

ptr = &Var2; /* Pointer now holds Var2 address */ *ptr = 50; /* Var2 will change to be 50 */

23

Data types

Page 24: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Pointer size: Pointer size depends on a lot of factors (hardware, operating system, compiler, etc.), and not all pointer types on the same platform may have the same size.

For example, there are embedded processors that use a Harvard architecture, where code and data are in separate memory areas, and each may have a different address bus size (e.g., 8 bits for data, 16 bits for code). This means that object pointers (int *, char *, double *) may be 8 bits wide, but function pointers (int (*)()) may be 16 bits wide.

So on 32-bits address bus controller:

char *ptr;

printf(‚%d‛, sizeof(ptr)); /* Print 4 bytes */

24

Data types

Page 25: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Pointer arithmetic: A pointer in c is an address, which is a numeric value. Therefore, you can perform arithmetic operations on a pointer just as you can on a numeric value but the result is different than normal variables. There are four arithmetic operators that can be used on pointers: ++, --, +, and - .

When we type:

long int Var1 = 10;

long int *ptr = &Var1;

ptr ++;

If Var1 address is for example 1000, then pointer value at the start is the same as Var1 address which is 1000.

After ptr++ the value of ptr will become 1004, which means it is incremented by 4 not just 1.

Pointer increment by 1 will increment pointer value by the size of the variable it points at which is 4 bytes in our case.

25

Data types

Page 26: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Another example:

long int Var1 = 10;

long int *ptr = &Var1;

ptr += 3;

If Var1 address is for example 1000, then pointer value at the start is the same as Var1 address which is 1000.

Adding 3 to ptr will make ptr value becomes 1012, which means it is incremented by 12 (3 * 4 bytes = 12).

The same for other increment and decrement operations.

26

Data types

Page 27: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Pointer can point to any data type, for example pointer to array:

long int Arr[5] = {10,20,30,40,50};

long int *ptr = &Arr[0];

This will make ptr points at the first element in the array so:

printf(‚%d‛, *ptr); /* Will print 10 */

printf(‚%d‛, *(ptr+1)); /* Will print 20*/

printf(‚%d‛, *(ptr+2)); /* Will print 30*/

*(ptr+4) = 0; /*Will change last element in Arr to 0*/

Pointer can also be used like array with index:

printf(‚%d‛, ptr[2]); /* Will print 30 */

ptr[4] = 3; /*Will change last element in Arr to 3*/

27

Data types

Page 28: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Pointer can be used to represent string like arrays used:

char *ptr = ‚Hello‛;

printf(‚%s‛, ptr); /* Will print Hello */

This is the same as typing:

char ptr[] = ‚Hello‛;

Note: Array name is itself a pointer to the first element in the array, but it is a constant pointer so Arr value can’t be changed by any means:

char Arr[6] = ‚Hello‛;

Arr++; /* Compilation error */

28

Data types

Page 29: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

We can create an array of pointers:

long Var1 = 1, Var2 = 2, Var3 = 3;

long int *ptr[3] = {&Var1, &Var2, &Var3};

/* Now each element in ptr array is a pointer to variable */

printf(‚%d‛, *ptr[0]); /* Will print 1*/

printf(‚%d‛, *ptr[1]); /* Will print 2*/

printf(‚%d‛, *ptr[2]); /* Will print 3*/

29

Data types

Page 30: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

We can have a pointer to pointer, which means that a pointer 2 holds the address of pointer 1, and pointer 1 points to any variable:

long Var1 = 1;

long int *ptr1 = &Var1; /* ptr1 points to Var1 */

long int **ptr2 = &ptr1; /* ptr2 points to ptr1 */

30

Data types

*ptr1 = 10; /* Will change Var1 value to 10 */

**ptr2 = 20; /* Will change Var1 value to 20 */

*ptr2 = NULL; /* Will change value of ptr1 to NULL */

Var1 = 1 Address 0xFF00 ptr1 = 0xFF00

Address 0xFFE4 ptr2 = 0xFFE4

Address 0xFFF8

Var1 = 20 Address 0xFF00 ptr1 = NULL

Address 0xFFE4 ptr2 = 0xFFE4

Address 0xFFF8

Page 31: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

We can make a pointer to function: A function pointer is a variable that stores the address of a function that can later be called through that function pointer.

int myFunc(char myChar)

{

printf(‚Character is %c‛, myChar);

return 0;

}

/* inside any code */

int (*ptr)(char) = myFunc;

/* Now ptr holds the address of function myFunc */

ptr(‘A’); /* Will call myFunc which will print A*/

(*ptr)(‘A’); /* The same as previous line*/

31

Data types

Page 32: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Pointers examples: what is the difference between all of this?

int a;

int *a;

int **a;

int a[10];

int *a[10];

int (*a)[10];

int (*a)(int);

int (*a[10])(int);

32

Data types

Page 33: Embedded C - Lecture 2

Derived Data Types (Pointers continue)

Pointers examples:

int a; /* An integer */

int *a; /* A pointer to an integer */

int **a; /* A pointer to a pointer to an integer */

int a[10]; /* An array of 10 integers */

int *a[10]; /* An array of 10 pointers to integers */

int (*a)[10]; /* A pointer to an array of 10 integers */

int (*a)(int); /* A pointer to a function a that takes an integer argument and returns an integer */

int (*a[10])(int); /* An array of 10 pointers to functions that take an integer argument and return an integer */

33

Data types

Page 34: Embedded C - Lecture 2

User Defined Data Types

Sometimes, the basic set of data types defined in the C language such as int, float etc. may be insufficient for your application. In circumstances such as these, you can create your own data types which are based on the standard ones.

Structure: a kind of data structure that can store a collection of elements that are not of the same type. For example:

struct carInfo {

int speed;

float batteryLevel;

double kilometers;

char bodyTemperature;

};

This is called structure carInfo data type declaration, but till now there is no variable of that type exists.

34

Data types

Page 35: Embedded C - Lecture 2

User Defined Data Types (Structure continue)

To declare a variable of type carInfo:

struct carInfo myCar; /* Uninitialized */

OR

struct carInfo myCar = {0, 0, 0, 0}; /*Initialized*/

To edit any value of variable structure members:

myCar.speed = readCarSpeed();

myCar.batteryLevel = readBatteryLevel();

myCar.kilometers = readCarKilometers();

myCar.bodyTemperature = readBodyTemperature();

35

Data types

Page 36: Embedded C - Lecture 2

User Defined Data Types (Structure continue)

We can define a pointer to structure:

struct carInfo *ptr = &myCar; /*ptr points to myCar*/

Editing structure members through pointer is achieved through arrow operator ->

ptr->speed = readCarSpeed();

36

Data types

Page 37: Embedded C - Lecture 2

User Defined Data Types

Union: A union is a special data type available in C that allows to store different data types in the same memory location. You can define a union with many members, but only one member can contain a valid value at any given time. Unions provide an efficient way of using the same memory location for multiple-purpose:

union U_data {

int Var1;

double Var2;

char Var3;

};

To declare a variable of type U_data :

union U_data test; /* Uninitialized */

OR

union U_data test = {0}; /*Initialized*/

37

Data types

Page 38: Embedded C - Lecture 2

User Defined Data Types (Union continue)

Union members share the same memory location, so editing any member’s value will corrupt other members’ values:

test.Var1 = 32; /* all other members are now 32*/

test.Var2 = 10; /* first 2 bytes of the union memory contains value 10, other 2 bytes are not changed*/

Size of Union variable test is the size of the biggest union member (double in our example).

Using union saves memory, but a big caution should be taken to track which member contains valid data.

38

Data types

Page 39: Embedded C - Lecture 2

User Defined Data Types

Enum: An enumeration is a user-defined data type consists of integral constants and each integral constant is give a name:

enum week {sunday, monday, tuesday, wednesday, thursday,

friday, saturday};

First element in enum by default takes value 0 if not initialized explicitly, so sunday will have value 0, monday will have value 1 and so on.

Usage:

enum week today; /*Declare variable of type enum week*/

today = Wednesday; /* today value now is 3*/

39

Data types

Page 40: Embedded C - Lecture 2

User Defined Data Types (Enum continue)

enum members can be initialized with default values:

enum week {sunday=5,monday,tuesday,Wednesday=9,thursday, friday, saturday};

Now sunday will have value 5, monday 6, tuesday 7, but wednesday will be 9 not 8, then thursday 10 and so on.

Note: Multiple members can have the same value, but member name can’t exist twice.

We can add boolean data type to C language using enum:

enum boolean{ false, true};

enum boolean check; /* Now we can use check as boolean */

40

Data types

Page 41: Embedded C - Lecture 2

User Defined Data Types

typedef: We can define any data type we want from another data type using typdef keyword:

typedef unsigned char uint8;

typedef unsigned short uint16;

typedef unsigned long uint32;

typedef unsigned long long uint64;

Now we can use the new defined data types:

uint8 Var1; /*Var1 is unsigned char*/

41

Data types

Page 42: Embedded C - Lecture 2

User Defined Data Types (typedef continue)

typedef with structure:

typedef struct carInfo {

unsigned long speed;

unsigned short batteryLevel;

unsigned long kilometers;

unsigned char bodyTemperature;

} Car_Type;

Now instead of typing:

struct carInfo myCar;

We can type:

Car_Type myCar;

42

Data types

Page 43: Embedded C - Lecture 2

User Defined Data Types (typedef continue)

We can simulate the behavior of typedef using #define like that:

#define uint8 unsigned char

uint8 Var1, Var2;/*Var1 and Var2 are now unsigned char*/

But the problem arises when trying something like that:

typedef unsigned char* u8_ptr_Type;

#define u8_ptr_Define unsigned char*

u8_ptr_Type ptr1, ptr2; /* Both will be pointers */

u8_ptr_Define ptr3, ptr4;

It is expected that both ptr3 and ptr4 are of type pointers to unsigned char, but because #define is just text replacement then what really happens is that previous line is replaced with:

unsigned char* ptr3, ptr4;

Which will make ptr3 successfully a pointer, but ptr4 will be normal variable.

43

Data types

Page 44: Embedded C - Lecture 2

Declaration vs. Definition

Declaration: A declaration tells the compiler the type of a variable, object or function, but doesn’t allocates memory for it or know all function details.

extern uint8 Var1; /* Variable extern */

void myFunc(uint8 Var2); /* Function prototype */

This is particularly useful if you are working with multiple source files, and you need to use a function or variable in multiple files. You don't want to put the body of the function or same variable in multiple files statically, but you do need to provide a declaration for it.

Then in only one file you can put the definition.

44

Data types

Page 45: Embedded C - Lecture 2

Declaration vs. Definition

Definition: A definition allocates memory for a variable or object and is the implementation of a function.

uint8 Var1 = 10;

void myFunc(uint8 Var2) /* Function Definition*/

{

printf(‚myFunc definition\n‛);

}

Multiple declarations are allowed, but only one definition.

45

Data types

Page 46: Embedded C - Lecture 2

Overflow vs. Underflow

Overflow: It is the condition that occurs when a calculation produces a result that is greater in magnitude than the maximum value that a given register or storage location can store or represent.

Unsigned overflow:

unsigned char Var1 = 255;

Var1++; /* Overflow */

printf(‚%d‛, Var1); /* Prints Zero */

Signed overflow:

signed char Var2 = 127;

Var2++; /* Overflow */

printf(‚%d‛, Var2); /* Prints -128*/

46

Data types

Page 47: Embedded C - Lecture 2

Overflow vs. Underflow

Underflow: It is the condition that occurs when a calculation produces a result that is smaller than the minimum value that a can be stored.

Unsigned underflow:

unsigned char Var1 = 0;

Var1 -= 8; /* Underflow */

printf(‚%d‛, Var1); /* Prints 248*/

Signed underflow:

signed char Var2 = -128;

Var2 -= 3; /* Underflow */

printf(‚%d‛, Var1); /* Prints 125*/

47

Data types

Page 48: Embedded C - Lecture 2

Type Casting

Type casting is a way to convert a variable from one data type to another data type. For example, if you want to store a 'long' value into a simple integer then you can type cast 'long' to 'int'.

Explicit casting: You can convert the values from one type to another explicitly using the cast operator as follows:

(type_name) expression

Example:

int Var1 = 8, Var2 = 20;

float Var3 = (float) Var2 / Var1;

Implicit casting: Implicit casting doesn't require a casting operator. This casting is normally used when converting data from smaller integral types to larger or derived types to the base type:

Var3 = Var2; /* Var2 value is implicitly converted to

float and put inside Var3*/ 48

Data types

Page 49: Embedded C - Lecture 2

Data Type Qualifiers

49

Page 50: Embedded C - Lecture 2

Data Type Qualifiers

• Apart from the mentioned primitive data types, there are certain data type qualifiers that can be applied to them in order to alter their range and storage space and other properties to fit in various situations as per the requirement.

• The data type qualifiers available in c are:

Size modifier: affects size (number of bytes):

short int variable_name; /* 2 bytes integer */

long int variable_name; /* 4 bytes integer */

Sign modifier: affects values range:

signed int variable_name; /* can have negative range */

unsigned int variable_name; /*can’t have negative range*/

Constant modifier: affects ability to change it during runtime:

const char Var = ‘A’; /* can’t change at runtime */

50

Data Type Qualifiers

Page 51: Embedded C - Lecture 2

Primitive Data Types Summary with size and sign modifiers

51

Data type Size Value range

char 1 -128 to 127 or 0 to 255

unsigned char 1 0 to 255

signed char 1 -128 to 127

int 2 or 4 -32,768 to 32,767 or -2,147,483,648 to

2,147,483,647

unsigned int 2 or 4 0 to 65,535 or 0 to 4,294,967,295

short 2 -32,768 to 32,767

unsigned short 2 0 to 65,535

long 4 -2,147,483,648 to 2,147,483,647

unsigned long 4 0 to 4,294,967,295

Data Type Qualifiers

Page 52: Embedded C - Lecture 2

Primitive Data Types Summary with size and sign modifiers

52

Data type Size Value range Precision

float 4 1.2E-38 to 3.4E+38

6 decimal places

double 8 2.3E-308 to 1.7E+308

15 decimal places

long double 10 3.4E-4932 to 1.1E+4932

19 decimal places

Data Type Qualifiers

Page 53: Embedded C - Lecture 2

Data Type Qualifiers

It should be noted that the size and sign modifiers cannot be applied to float and can only be applied to integer and character data types.

Volatile modifier: tells the compiler that this variable can change outside of the code control (ex. HW Register value):

volatile char Var = 10;

if (Var == 10)

{

/* Any code*/

/* If variable was not declared volatile, the compiler may optimize this part by considering the condition always True and remove the if condition check ! */

}

When a variable is declared volatile, any reference for that variable makes the compiler reload its value from RAM again to make sure it is the last updated version of that variable. 53

Data Type Qualifiers

Page 54: Embedded C - Lecture 2

Data Type Qualifiers

Storage classes: A storage class defines the scope (visibility) and life-time of variables and/or functions within a C Program.

o auto: The auto storage class is the default storage class for all local variables.

int variable_name = 20;

o register: The register storage class is used to define local variables that should be stored in a register instead of RAM. This means that the variable has a maximum size equal to the register size and can't have the unary '&' operator applied to it (as it does not have a memory location).

/* Compiler will try to put this variable inside

processor registers */

register int variable_name = 10;

54

Data Type Qualifiers

Page 55: Embedded C - Lecture 2

Data Type Qualifiers

Storage classes:

o static: The static storage class instructs the compiler to keep a local variable in existence during the life-time of the program instead of creating and destroying it each time it comes into and goes out of scope. Therefore, making local variables static allows them to maintain their values between function calls.

The static modifier may also be applied to global variables or functions. When this is done, it causes that variable or function’s scope to be restricted to the file in which it is declared.

o extern: The extern storage class is used to give a reference of a global variable or function that is visible to ALL the program files. When you use 'extern', the variable cannot be initialized and function can’t have a body however, it points to variable or function name that has been previously defined in another file.

55

Data Type Qualifiers

Page 56: Embedded C - Lecture 2

Data Type Qualifiers

Storage classes:

56

File1.c static int variable_1 = 10; int variable_2 = 20;

File2.c static int variable_1 = 50; extern int variable_2; /* Then inside any part of code */ printf(‚variable_1=%d, variable_2=%d‛, variable_1, variable_2); /* It will print variable_1=50, variable_2=20 */

Data Type Qualifiers

Page 57: Embedded C - Lecture 2

Scope and Lifetime

57

Page 58: Embedded C - Lecture 2

Scope

58

Scope and Lifetime

Local scope

File scope

Global scope

Page 59: Embedded C - Lecture 2

Scope

• The area of our program where we can actually access our variable is the scope of that variable.

Local scope: visible within function or statement block from point of declaration until the end of the block.

void myFunc()

{

uint8 Var1 = 10;

{

uint8 Var2 = 11;

printf(‚%d, %d‛, Var1, Var2);

}

printf(‚%d‛, Var2);

}

Var1 is visible inside all myFunc, but Var2 is only visible inside its inner block { } which is a local inner scope.

59

Scope and Lifetime

Page 60: Embedded C - Lecture 2

Scope

File scope: visible within current file only, but its is visible to all functions inside this file, by using static keyword with variables or functions.

60

Scope and Lifetime

File1.c static int Var1 = 50; void myFunc1() { printf(‚%d‛, Var1); } void myFunc2() { printf(‚%d‛, Var1); }

File2.c void myFunc3() { printf(‚%d‛, Var1); }

File3.c static int Var1 = 20; /* Same name, but totally different variable */

Page 61: Embedded C - Lecture 2

Scope

Global scope: visible everywhere, it is defined in one file, and all other files can use it using extern keyword.

61

Scope and Lifetime

File1.c int Var1 = 50; void myFunc1() { printf(‚%d‛, Var1); } void myFunc2() { printf(‚%d‛, Var1); }

File2.c extern int Var1; void myFunc3() { printf(‚%d‛, Var1); }

Page 62: Embedded C - Lecture 2

Lifetime

• Life time of any variable is the time for which the particular variable outlives in memory during running of the program.

Automatic: An automatic variable has a lifetime that begins when program execution enters the function or statement block or compound and ends when execution leaves the function or block. Automatic variables are stored in a "function call stack".

void myFunc()

{

uint8 Var1 = 10;

{

uint8 Var2 = 11;

/* Var2 scope and life time ends here */

}

/* Var1 scope and life time ends here */

} 62

Scope and Lifetime

Page 63: Embedded C - Lecture 2

Lifetime

Dynamic: The lifetime of a dynamic data begins when memory is allocated (e.g., by a call to malloc()) and ends when memory is deallocated (e.g., by a call to free()). Dynamic data are stored in "the heap".

/* Allocate 1000 bytes in RAM (Heap part) */

uint8 *myData = (uint8*) malloc(1000);

/* Any code */

free(myData); /* Data is deallocated, so its life time ends here */

63

Scope and Lifetime

Page 64: Embedded C - Lecture 2

Lifetime

Static: A static variable is stored in the data segment of the "object file" of a program. Its lifetime is the entire duration of the program's execution.

Static variable is initialized only the first time function is entered, and its value remains in memory between function calls.

void myFunc()

{

static uint8 Var1 = 10;

printf(‚%d‛, Var1);

Var1++;

}

First call to myFunc will print 10, second call 11, third call 12 and so on.

64

Scope and Lifetime

Page 65: Embedded C - Lecture 2

Makefile introduction

65

Page 66: Embedded C - Lecture 2

What is Make (GNU Make as an example)

• GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files.

• Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.

66

Makefile introduction

Page 67: Embedded C - Lecture 2

Capabilities of Make

• Make enables the end user to build and install your package (in case you provided them with source code) without knowing the details of how that is done because these details are recorded in the makefile that you supply.

• Make figures out automatically which files it needs to update, based on which source files have changed. It also automatically determines the proper order for updating files, in case one non-source file depends on another non-source file.

As a result, if you change a few source files and then run Make, it does not need to recompile all of your program. It updates only those non-source files that depend directly or indirectly on the source files that you changed.

• Make is not limited to any particular language. For each non-source file in the program, the makefile specifies the shell commands to compute it. These shell commands can run a compiler to produce an object file, the linker to produce an executable, ar to update a library..etc.

67

Makefile introduction

Page 68: Embedded C - Lecture 2

makefile structure

• Make searches for a file called makefile without any extension to do its work.

• Basic building elements of make file are:

Target: What is the output of current step.

Dependencies: What should be already available to start making current target.

Commands: What should be done to make current target.

All of them are called “Rule”. A rule in the makefile tells Make how to execute a series of commands in order to build a target file from source files. It also specifies a list of dependencies of the target file. This list should include all files (whether source files or other targets) which are used as inputs to the commands in the rule.

68

Makefile introduction

Page 69: Embedded C - Lecture 2

makefile structure

• Rule structure:

69

Makefile introduction

• Target name is typed at start of line, then followed by a colon : then followed by list of all dependencies that should be available to start executing commands, then on each new line write a new command to be executed, each command should have a Tab space before it or the make will not work.

• Dependencies can be on one line, or can be divided on multiple lines:

Target: dependency1 dependency2 ...

commands

...

Target: dependency1 dependency2\

dependency3 ...

commands

...

Page 70: Embedded C - Lecture 2

makefile structure

• Rule example:

70

Makefile introduction

D:\newProject>make file.o

file.o: file.c

gcc -c file.c -o file.o

Create any C code file with the name file.c and put it in the same directory with the makefile.

Then by running Make through command line:

Create a text file with the name makefile without any extension and type the following inside it:

The Make utility will search for the file makefile, then it will search inside it for the required target file.o .

It will search what are file.o dependencies, it will find that it only depends on file.c.

Page 71: Embedded C - Lecture 2

makefile structure

It will check “is the required dependency file.c available ?”, the Make file will find it in the same directory.

After the Make utility finds file.c, it will execute the commands under file.o target which is here compiling file.c, so the output of this command will be:

71

Makefile introduction

D:\newProject>make file.o gcc -c file.c -o file.o

Now you will find in the same directory a file created called file.o which is the compiled output file of file.c .

Page 72: Embedded C - Lecture 2

makefile structure

• If the Make didn’t find the required dependency file, it will search if this dependency is another required target then do it first:

72

Makefile introduction

app.exe: file1.o file2.o

gcc file1.o file2.o -o app.exe

file1.o: file1.c

gcc -c file1.c -o file1.o

file2.o: file2.c

gcc -c file2.c -o file2.o

Create a C code file with the name file1.c which contains the main function, create another C code file file2.c with any code, and put them in the same directory with the makefile.

Page 73: Embedded C - Lecture 2

makefile structure

73

Makefile introduction

Run the Make and the output will be:

D:\newProject>make app.exe gcc -c file1.c -o file1.o gcc -c file2.c -o file2.o gcc file1.o file2.o -o app.exe

Now you will find in the same directory a file called app.exe which is the compilation output.

Page 74: Embedded C - Lecture 2

makefile structure

• If the Make didn’t find the required dependency file in the directory or as any other target then it will generate this error and stop:

74

Makefile introduction

D:\newProject>make newFile.c make: *** No rule to make target `newFile.c'. Stop.

• If Tab space doesn’t exist before any command then Make will generate this error and stop (note that 5 is number of first line containing error):

D:\newProject>make app.exe makefile:5: *** missing separator. Stop.

• When you run Make, you can specify particular targets to update; otherwise, Make updates the first target listed in the makefile. Of course, any other target files needed as input for generating these targets must be updated first.

Page 75: Embedded C - Lecture 2

makefile structure

• Usually makefile contains 2 targets called all and clean to be used by any one without knowing the details of our makefile content:

75

Makefile introduction

# all target is usually put as the first target

all: app.exe

clean:

rm file1.o file2.o app.exe

app.exe: file1.o file2.o

gcc file1.o file2.o -o app.exe

file1.o: file1.c

gcc -c file1.c -o file1.o

file2.o: file2.c

gcc -c file2.c -o file2.o

Page 76: Embedded C - Lecture 2

makefile structure

• So typing all target will output the same result as typing app.exe, and typing clean target will delete file1.o, file2.o and app.exe .

76

Makefile introduction

D:\newProject>make clean rm file1.o file2.o app.exe

• clean and all targets are called “Phony Targets”. A phony target is one that is not really the name of a file, rather it is just a name for a recipe to be executed when you make an explicit request, it may have dependencies like all target and may not have any dependencies like clean target.

Page 77: Embedded C - Lecture 2

Make variables

• A variable begins with a $ and is enclosed within parentheses (...) or braces {...}. Single character variables do not need the parentheses.

Example:

77

Makefile introduction

CC = gcc

file1.o: file1.c

$(CC) -c file1.c -o file1.o

Page 78: Embedded C - Lecture 2

Make variables

• Automatic Variables:

Automatic variables are set by make after a rule is matched. They include:

$@: the target filename.

$*: the target filename without the file extension.

$<: the first prerequisite filename.

$^: the filenames of all the prerequisites, separated by spaces, discard duplicates.

$+: similar to $^, but includes duplicates.

$?: the names of all prerequisites that are newer than the target, separated by spaces.

78

Makefile introduction

Page 79: Embedded C - Lecture 2

Make variables

Example 1:

79

Makefile introduction

file1.o: file1.c

$(CC) -c $< -o $@

# It is the same as typing $(CC) -c file1.c -o file1.o

Example 2:

OBJ_FILES = file1.o file2.o

app.exe: $(OBJ_FILES)

$(CC) $^ -o $@

# It is the same as typing:

# $(CC) file1.o file2.o -o app.exe

Page 80: Embedded C - Lecture 2

Make variables

Example 3:

80

Makefile introduction

OBJ_FILES = file1.o file2.o

LINK_TARGET = app.exe

CLEAN_TARGET = $(LINK_TARGET) $(OBJ_FILES)

all: $(LINK_TARGET)

# It is the same as typing:

# all: app.exe

clean:

rm $(CLEAN_TARGET)

# It is the same as typing:

# clean:

rm file1.o file2.o app.exe

Page 81: Embedded C - Lecture 2

Make implicit rules

Implicit rules are a set of generalized instructions for doing certain tasks, where the instructions are provided as default.

For example, an implicit rule can tell Make utility how to construct a .o file from a .c file without explicitly typing files names.

Example 1:

81

Makefile introduction

%.o: %.c

$(CC) -c $< -o $@

# It means ‚whenever you find a required target with #any name but with extention .o and there is no #explicit rule for how to make this target, then #execute the next commands on this target.

Page 82: Embedded C - Lecture 2

Make implicit rules

Example 2:

82

Makefile introduction

%.o: %.c

$(CC) -c $< -o $@

file1.o: file1.c

$(CC) -c file1.c -o explicit_file1.o

app.exe: file1.o file2.o file3.o

$(CC) $^ -o $@

# For file2.o and file3.o it will use the implicit #rule, so their output names will be file2.o and #file3.o but for file1.o it will find an explicit rule #for it then it will execute it, so its output name #will be explicit_file1.o

Page 83: Embedded C - Lecture 2

Creating project hierarchy

Most of the time the project contains different folders, not just all source and headers files in the same place, for example if we need to make one folder for source code files with name “src” and another folder for header files with name “inc”, then in the makefile we should do the following (assuming that the makefile is in the same directory as both folders):

83

Makefile introduction

# Tell makefile Where to find source files

vpath %.c ./src

# Header files path

INCLUDE_PATH = ./inc

%.o: %.c

$(CC) -c -I$(INCLUDE_PATH) $< -o $@

# -I$(INCLUDE_PATH) tells the compiler where to find #header files

Page 84: Embedded C - Lecture 2

Header files issue

After running make all, if we edited any source file the Make utility will recompile this file only and targets that depends on it.

But if we changed any header file the make utility will not even notice and will not make any recompilation for the source code that depends on this file which we don’t want. This is because we didn’t tell the Make utility any thing about header files dependencies inside the makefile.

That’s why we should make dependencies generation, which makes the compiler enters all source files, extract information about what header files that each source file depends on, generate a corresponding dependency file for this source file and include this information in our makefile.

After making these steps, whenever we change a header file, any source file that depends on this header file will be recompiled and any target that depends on this source file will be made again.

84

Makefile introduction

Page 85: Embedded C - Lecture 2

Header files issue (solution)

85

Makefile introduction

# Generate dependencies list

# Dependencies output path

DEPS_PATH = ./dep/

# Source files path

SRC_PATH = ./src/

# Header files path

INCLUDE_PATH = ./inc/

# Get source files names with path

SRC_FILES_ABSOLUTE = $(wildcard $(SRC_PATH)*.c)

# Get source files names without path

SRC_FILES = $(patsubst $(SRC_PATH)%.c,%.c,$(SRC_FILES_ABSOLUTE))

Page 86: Embedded C - Lecture 2

Header files issue (solution)

86

Makefile introduction

# Make dependencies names the same as sources names but with extension .d

DEPS_FILES = $(patsubst %.c,%.d,$(SRC_FILES))

# Get dependencies files names with their path

DEPS_FILES_ABSOLUTE = $(patsubst %.d,$(DEPS_PATH)%.d,$(DEPS_FILES))

# Rule to make dependencies files

$(DEPS_PATH)%.d : %.c

# Generate dependencies files (Small make file for each c file)

$(CC) -MM -MP -MT$@ -I$(INCLUDE_PATH) $< -o $@

Page 87: Embedded C - Lecture 2

Header files issue (solution)

87

Makefile introduction

# Include all dependencies make files inside current make to be #used in #linking target to detect when any header file changes

-include $(DEPS_FILES_ABSOLUTE)

# Add dependencies to our target

app.exe: $(OBJ_FILES) $(DEPS_FILES_ABSOLUTE)

$(CC) $^ -o $@

Page 88: Embedded C - Lecture 2

Make simple complete example

Create 3 different folders:

• src: contains all your source files.

• inc: contains all your header files.

• dep: empty, will be the output for dependencies files.

Create empty text file with the name makefile without any extension and put the following inside the file:

88

Makefile introduction

Page 89: Embedded C - Lecture 2

Make simple complete example

89

Makefile introduction

# Tell makefile Where to find source files

vpath %.c ./src

# Compiler used

CC = gcc

# Dependencies output path

DEPS_PATH = ./dep/

# Source files path

SRC_PATH = ./src/

# Header files path

INCLUDE_PATH = ./inc/

Page 90: Embedded C - Lecture 2

Make simple complete example

90

Makefile introduction

# What is our target name

LINK_TARGET = app.exe

# Objects used later to be linked to generate our link target

OBJ = main.o\

Func1.o\

Func2.o\

Func3.o

# What will be used at clean target

CLEAN_TARGET = $(LINK_TARGET) $(OBJ)

# Used with > make all

all:$(LINK_TARGET)

echo Bulding done !

Page 91: Embedded C - Lecture 2

Make simple complete example

91

Makefile introduction

# Used with > make clean

clean:

rm $(CLEAN_TARGET)

echo Cleaning done !

# What will happen when requiring $(LINK_TARGET)

$(LINK_TARGET): $(OBJ) $(DEPS_FILES_ABSOLUTE)

$(CC) $(OBJ) -o $@

echo Linking done !

# What will happening when requiring any target with any file name with #extension .o

%.o: %.c

$(CC) -c -I$(INCLUDE_PATH) $< -o $@

echo File $< compilation done !

Page 92: Embedded C - Lecture 2

Make simple complete example

92

Makefile introduction

# Generate dependencies list

# Get source files names with path

SRC_FILES_ABSOLUTE = $(wildcard $(SRC_PATH)*.c)

# Get source files names without path

SRC_FILES = $(patsubst $(SRC_PATH)%.c,%.c,$(SRC_FILES_ABSOLUTE))

# Make dependencies names the same as sources names but with extension .d

DEPS_FILES = $(patsubst %.c,%.d,$(SRC_FILES))

# Get dependencies files names with their path

DEPS_FILES_ABSOLUTE = $(patsubst %.d,$(DEPS_PATH)%.d,$(DEPS_FILES))

Page 93: Embedded C - Lecture 2

Make simple complete example

93

Makefile introduction

# Rule to make dependencies files

$(DEPS_PATH)%.d : %.c

#Generate dependencies files (Small make file for each c file)

$(CC) -MM -MP -MT$@ -I$(INCLUDE_PATH) $< -o $@

# Include all dependencies files inside current make to be used in #linking target to detect when any header file changes

-include $(DEPS_FILES_ABSOLUTE)

Page 94: Embedded C - Lecture 2

Task 2

94

Page 95: Embedded C - Lecture 2

Mohamed AbdAllah Embedded Systems Engineer [email protected]

95