Anda di halaman 1dari 63

A Crash Course in C

For Embedded Systems


Reference: Robert Hodge

What will I learn from this


presentation?
A conceptual overview of C
A brief introduction to soften the shock
of having to learn a new language
Highlights of main points of C language
Some pointers as to what is important
and what isnt important in C when its
used in embedded systems

What will I NOT learn from


this presentation?
You wont learn everything about C, or even
everything you need to finish your course work
Learn how to use the IDE help facilities
Get a good book on C not a huge telephone
book sized one youll never have time to read it
There are commercially available Crash Course
books on C find one
Ask questions lots of people know C
Experiment write little 10-line or 20-line
programs to test ideas and understanding of C; see
what compile does or will accept

How does embedded C differ


from conventional C?
Same language used for different purposes
Fewer libraries
Fewer I/O capabilities (usually no printer, etc.
attached to embedded device)
More reliance on low-level facilities (absolute
addresses, memory mapping requirements for code
and data)
Hardware-specific libraries for ports, timers, DSP
facilities, etc.
Sometimes non-standard keywords & #pragmas
Bottom line: generally simpler than PC-based C

Lexical: Source Code


Conventions
Source code written in standard ASCII
Lines may end in LF or CR/LF, depending on
host/compiler
C is a free-format language, not line-oriented
C preprocessor is not part of C language per se,
and is line-oriented
Preprocessor lines and string literals sometimes
need to be continued on subsequent lines by
ending the line with a backslash \ character
C source program files normally have a file name
extension of .c

Lexical: Whitespace
Whitespace in source code is one or more
unprintable characters: space, CR, LF, HT, VT, FF
Other unprintable characters are illegal outside of
quotes; should not be used directly inside of
quotes (use hex notation instead)
Whitespace is ignored by compiler, except where
needed to separate identifiers from keywords, etc.
It is best to completely avoid VT and FF
Use of tabs (HT) vs. spaces for indentation is a
matter of taste, convenience, adherence to project
standards and facilities offered by IDE.

Lexical: Identifiers
Identifiers are case-sensitive: abc and ABC are
different names; if is a reserved word, IF is not
Allowable characters: A-Z, a-z, 0-9, _
Cannot start with 0-9
Sometimes $ is allowed, but generally should be
avoided
Underscores may start identifier, but some uses
are reserved for special/system names; be careful
Modern compilers dont restrict identifier length;
check compiler for specific limitations

Lexical: Identifiers
All-capital names are generally reserved for
constants (const) and #define names
Initial-capital names sometimes used as a
convention for type names (typedef,
struct, union)
Naming styles vary by taste:
threePartName
three_part_name

Lexical: Comments
A comment is equivalent to whitespace; it
acts as a separator, and is otherwise ignored
C comments start with /* and end with */
No spaces between / and * are allowed
C comments do not nest
/* this /* is */ bad */

Comments may span multiple lines:


/*
comment
*/

Lexical: Comments
Many C compilers have features borrowed
from C++ compilers
C++ style line-comments may be available
These comment starts with //, ends with end
of line
Not strictly ANSI-compliant language, but
often useful
When combining /* and // comments:
whoever gets there first, gets it

Lexical: Comments
Convention to comment-out a large block of
code: use preprocessor #if 0
Because its a preprocessor block construct, it
is nestable
Additional preprocessor commands discussed
later
Example:
#if 0
this line is commented out
#endif

Lexical: Integer constants


Decimal values: write without leading 0
Octal values: write with leading 0
077 same as decimal 63

Hex values: write with leading 0x or 0X;


digits higher than 9 can be a-f or A-F
0xFF same as decimal 255

For readability: use 0x, not 0X, and use


upper case for A-Z instead of a-f

Lexical: Integer constants


Integer constants can be long or regular sized
Long constants have a trailing L or l
Example: 1234L, 0xA5L
Readability: use L instead of l, because l (el)
looks like 1 (one)
Meaning of long depends on compiler and
hardware; it may be 16, 32 or 64 bits
On embedded systems, long may have other
(non-standard) meanings; check the compilers
documentation

Lexical: Integer constants


Integer constants can be considered signed
or unsigned
Default is signed
Unsigned integer constants are often used for
absolute addresses in embedded systems
Constant can be made unsigned by U or u
appended to value; example
1234U, 0xA5U

Can be combined with long notation in any


order or case: UL, LU, ul, lu, uL, etc.

Lexical: Integer precision


Precision of integer constants and int data type is
hardware dependent
Small machines: int is usually 16 bits
Large machines: int is usually 32 bits
#include "limits.h" defines floating-point
limits
Can use typedef keyword for user-defined types,
to hide these differences (portability)

Lexical: Characters
A character is enclosed in single quotes, and defines
one and only one 8-bit character
A character is of data type char
Backslash notation can be used to escape special
characters, octal or hex values
See compiler documentation for complete list of
special escaped values. Most common are '\n' for
newline (LF) and '\t' for horizontal tab (HT)
Literal backslash or quote character must be escaped
'a' '\033' '\xFF'
'\'' '\\'

Lexical: Characters
Value of a character is an integer, equal to the
collating sequence of the character in ASCII
Check compiler to see if char data type is signed or
unsigned
' ' numerically the same as 32 (0x20)
'1' numerically the same as 49 (0x31)

Lexical: Unicode Characters


Unicode is 16-bit character set
Data type is wchar_t
wchar_t usually a synonym for unsigned
short int

Notation is L'x'
Limited usage in embedded systems

Lexical: Strings
Strings are zero or more ASCII characters enclosed
in double quotes
Strings are of data type char * (pointer to
character)
"Hello, World"
C compiler automatically adds a binary-zero
character at the end of a string literal in memory
This zero-byte is called the null terminator
Strings have limited use in embedded systems

Lexical: Strings
The value of a string is a pointer, a
memory address
A string of length one is not a character
'x'

is not the same as "X"

Escaped characters in strings basically


use same rules as for char values

Lexical: String Continuation


A string too long to fit on one line can be written
on multiple lines
Standard C method: use backslash on one line,
continue string in column 1 of next line
str = "Hello, \
World";
ANSI C and C++ method: pieces of string are
concatenated at compile time, without backslash
str = "Hello, "
"World";

Lexical: Floating-Point Values


Floating-point values in C are much like other
languages
Requires a decimal point and/or an exponent to
distinguish from integer
Exponent format is [+|-] [E|e] expon
#include "float.h" defines floating-point
limits
Examples:
123.0 0.123 .123 123. 1E5 2.3e-4

Lexical: Floating-Point Values


Floating-point literals default to type double (64-bit
IEEE format)
Can force a value to type float (32-bit IEEE format)
by F or f suffix
Can explicitly label a value as double by D or d suffix
Compiler can convert between float and double
in expressions and in function parameters; choice
depends on application, available memory, etc.
Examples:
123.4F 123.4d

Lexical: Arithmetic Operators


Usual: + - * / < <= > >=
Modulus (remainder): %
Equal to, not equal to: == !=
Shifts: << >>
Logical and, or, not: && || !
Bitwise AND, OR, NOT, XOR: & | ~
Address-of: unary &
Pointer dereference: unary *
Pointer qualifier: ->
Struct/union member qualifier: .

Lexical: Assignment Operators


Usual: = used to assign to variables
Assignment is an operator, not a statement.
There is no assignment statement, only an assignment
expression, in C; multiple assignment goes right to left:
a = b = c;
Compound assignments perform arithmetic and assign;
a shorthand notation
Example (equivalent assignments)
a = a + b;
a += b;
Available compound operators:
+= -= *= /= %= <<= >>= &= |= ^=

Lexical: Increment/Decrement
Operators
Increment, decrement: ++ -These operators have side effects: they change a
variable in memory and produce a value result
Somewhat like compound operators += and -=
Example: x++; /* add 1 to x */
Two formats: prefix and postfix
Prefix format: ++x; first increments or
decrements a value by 1, then uses newly modified
value in rest of expression
Postfix format: x++; uses old value in
expression, then increments or decrements the
value by 1 later

Lexical: Increment/Decrement
Operators
Example:
int
y =
y =
y =
y =

x1=10,
++x1;
x2++;
--x3;
x4--;

x2=20, x3=30, x4=40, y;


// y == 11, x1 == 11
// y == 20, x2 == 21
// y == 29, x3 == 29
// y == 40, x4 == 39

Lexical: Comma Operator


Also called the sequential evaluation operator
Used where multiple expressions (possibly with
side-effects) must be used where one expression is
expected
Often seen in for loops
Do not use comma to separate subscripts in multidimensional arrays - it wont work. Be careful!
Example:
x = (y=1),(z=2); // y==1, x==2, z==2

Lexical: Conditional Operator


Also called a tertiary operator or ?: operator
A compound format, used as a short-hand to avoid
using an if statement
Format:
a = b ? c : d;
Meaning: if b is considered true, then a=c is
performed, else a=d
Recommendations: Dont overuse this syntax; use
parentheses; think twice before nesting them;
consider readability before using and while writing
such expressions

Data Type names: built-in


char: 8-bit integer (!) value; may be signed or
unsigned (check your compiler); can be used for
small values or characters
short: usually 16-bit value
int: usually 16-bit on embedded systems, and 32-bit
on Windows PC-based C
long: usually 32-bit value
float: usually 32-bit (single) IEEE floating-point
double: usually 64-bit (single) IEEE floating-point

Data type precision and typedef


There is no standard precision for char, short,
int and long
Only absolute guarantee on sizes:
char short int long
When precision is critical, use typedef to define a
user-defined type having a precision you need,
while keep the code portable
Example: I need a 32-bit (4-byte) integer type, and
the compiler's long type has it
typedef long int4;

Data type modifiers


unsigned: use entire precision of variable without a sign
bit; used with addresses and sometimes with characters
static: variable is allocated in memory at compile time, not
at run time
extern: variable is allocated in another source file, or in a
separately compiled function library
Modifiers are often combined with typedef
Examples:
unsigned char a;
static int b = 2;
extern long c;
typedef unsigned int uint;
uint d; // same as: unsigned int d;

Data type void


void is not an actual data type
main uses of void
the return-type of a function that doesnt return
any value
the type of a pointer not associated with any
particular data type; often used with memory
management functions
Examples:
void main() {} // main does not return a value
void *buffer; // buffer points to memory of
unknown type

Program Structure: declarations and


definitions
A C program consists of one or more external
declarations.
A declaration is a C statement that describes a
function or variable that is defined elsewhere
A definition is a C statement that allocates storage
for a variable, or creates executable code for a
function, at the point the definition appears
A definition acts as its own declaration, but not
vice-versa
C makes a big deal out of declaration vs. definition,
so its important you understand the distinction

Program Structure: externals


External declarations have an external attribute
associated with them
Sometimes the external attribute is implied by the
language and by usage; in other cases, the C
keyword extern is required
External declarations common to multiple source
files (in a modular program) should be included in
a header file (#include "abc.h") to keep the
definitions consistent
As a rule (but its not a law), we usually dont put
definitions in header files

Program Structure: Example


#include <stdio.h>
char msg[] = "Embedded Systems";
void main()
{
printf ("Hello, %s World\n", msg);
}
// output:

Hello, Embedded Systems World

Program Structure: main()


A function called main must appear as somewhere
in your program; it is the first function that
executes, and defines the starting point of the
program
A C runtime library actually executes first, then
calls main as a subroutine
The C runtime library for embedded systems is
usually very different than those used on a PCbased C compiler
See hardware vendor for specifics on runtime
library and main function for your embedded
system

Preprocessor: Basics
The C compiler has a preprocessor, which uses
directives embedded in your C program, to insert
additional C code, to selectively incorporate and/or
exclude code, and to selectively modify code
The resultant effective program is what is actually
compiled
Your source program is never actually changed
Old days: preprocessor actually created an
intermediate file which was then compiled
Now: preprocessor is integrated; creation of
intermediate file is done internally in one pass
All preprocessor directives start with #

Preprocessor: #include
#include copies files from an include library, which
are compiled as if the contents of the file were
actually part of your program
Included files are often referred to as header files,
since they usually appear at the head (the
beginning) of your program
Header files normally have a file extension of .h,
but can have any valid file name
Examples:
#include <stdio.h>
#include "myfile.h"

Preprocessor: #include
Compiler looks for included files using an include

path

Path usually defined by INCLUDE environment


variable or by IDE
Two kinds of include different search order
File name "abc.h" search local directory first,
then the include path
File name <abc.h> - only search include path;
normally used for system include files

Preprocessor: #include
Commonly used system include files
<stdio.h>
<stdlib.h>
<math.h>
<ctype.h>
<string.h>
<assert.h>
<malloc.h>
<time.h>

//
//
//
//
//
//
//
//

standard I/O definitions


standard function library
math functions
character classification
string handling functions
defines assert() macro
memory allocation functions
time functions

Preprocessor: #define
Used for substituting a symbol with a text value
Never use constants in code, use #define to define
constants with appropriate names.
The name introduced in this way is often called a
define name or a pound-define name
Definition may be a simple symbol or a parameterized
this is often called a macro, a define macro or a pound-

define macro

Long values may be continued on multiple lines using


backslash as continuation
Only name and value are used; no = or ; required
Examples:
#define PORT
0x100
#define NEG(x) (-x)

Preprocessor: #define
Rules and guidelines
Define names traditionally are written in UPPER
CASE; this is a convention, but not a requirement
For macros, no spaces allowed between name and
parameter list, or inside the list
If a name is already defined, defining it again is a
warning or error condition
A definition with no value is OK; it just makes a name
known to be defined to the compiler
#define TEST_MODE
It is possible to remove a definition of ABC using
#undef ABC

Preprocessor: #define
Traditional uses for #define:
Definitions of constants
Name substitution for variables and functions
Definitions of flags to control compile using #if
Selective debugging
Examples:
#define
#define
#define
#define

PORT 0x100
MYFUNCTION your_function
SUM(x,y) (x+y)
DEBUG_MODE

Preprocessor: #define
Defined names can also be introduced by commandline or through IDE
They appear to C program exactly like a name
defined using #define directive
This is often done to set up compile-time options
Typical format:
cc DNAME=value myprogram.c
Compiler has list of predefined names, like
__DATE__, __TIME__, __LINE__, __FILE__
See compiler documentation for details

Preprocessor: #if / #endif blocks


Defines a block of code to be included if a constant
expression is true
#elif and #else are optional; #endif is required
#if blocks are nestable
#if const_expr1
// code used if const_expr1 is true
#elif cond2
// code used if const_expr2 is true
#else
// code used if const_expr 1 and 2
// are both false
#endif

Preprocessor: #ifdef, #ifndef,


defined(x)
To select code if a name is defined:
#ifdef ABC
To select code if a name is not defined:
#ifndef ABC
defined(x) test can be used to test multiple
names and more complex tests:

#if defined(ABC) && (!defined(DEF))

Preprocessor: #pragma
Short for pragmatic comment
Used to define vendor-specific compiler
options
Typical use in embedded systems:
memory load addresses for functions
and static data (passed to linker)
See compiler documentation for details

Preprocessor: Additional directives


#line specifies line number and file
name; used in C code generated by
other software
#error displays an error message and is
treated like a compile error (compilation
will be unsuccessful)

Functions: (Fundamental Unit of C Program)


Only one function is named as main function. When program is
executed its starts its execution with main function.
Regarding functions one should clearly differentiate the
following:
1) Function Prototype
2) Function Definition
3) Function invocation or calling a Function.
Function prototype is also called function declaration or function
signature.
Prototype tells us the return value type of a function, number of
parameters of the function and the parameter types.
Function definition is the one, which contains the
implementation of the function.
Function invocation is nothing but the calling of a function.

return statement and control flow in a function


The return statement in a function serves two purposes, one is to return a value
to the calling function. Second one is to exit from the function, from in between
the function. If function is a void function, then no return statement is required.
Function automatically returns once it reaches end of the function.

Function parameters, Pass by Value and Pass by reference


In C by default parameters are passed by value only. If you want to pass by
reference of a variable, you have to do it explicitly by using & operator.

Input parameters and Output parameters of a function


Typically a function accepts all its input through the parameters. So these
parameters are called input parameters. In general if a function wants to output
a value to the calling function, then we can return a value through the return
statement. But if the function wants to return more than one value to the calling
function, then we will use output parameters. Output parameters are passed by
reference using & operator. Because of the reference the function can place the
output value in the variable, whose reference is given in the output parameter.
Eg. Void areaOfTriangle(int base, int height, int *area) has two input
parameters and one output parameter.

Function pointers
Pointer variables can be used to store addresses of functions. These pointer are
called function pointers. Function pointers are useful to implement callback
function mechanism, used in event driven programming.

Using a given function


using a already available function also requires good skills. First you should
understand the interface (or prototype) of that function well, so that you can
pass parameters correctly.
If function is returning some value , you should use it. Most of the functions
return the status as return value. Typical tendency of a programmer is to ignore
the return value.
Normally using of other function will happen in two cases
1) Using library functions.( stdio )
2) Using functions of other module being developed by other
members .
The second one requires more care. As library functions will be more tested they
do not require typical checks for validating the pointer variables for non null
value, and also checking the limits of the parameters.

Reentrant function:
Reentrant function is usable by the several tasks and routines synchronously (at the same
time). This is because all its argument values are retrievable from the stack. A function is
called reentrant function when the following three conditions are satisfied.
1) All the arguments pass the values and none of the argument is a pointer whenever a
calling function calls that function.
2) When an operation is not atomic, that function should not operate on any variable,
which is declared outside the function or which an interrupt service routine uses or which
is a global variable but passed by reference and not passed by value as an argument
into the function.
3)That function does not call any other function that is not itself Reentrant.

Macros and functions


Functions are used when the requirement is that the codes should be compiled once only.
However , on calling a function, the processor hat to save the context, and return, restore
the context (Toverheads). Further, a function may return nothing or return a Boolean
integer or any primitive or reference type of data. Macros are used when short functional
codes are to be inserted in a number of places or in functions.
We use function when the Toverheads << Texec
and a macro is used when Toverheads ~= or > Texec

Pointers:
Concept of Address and Size
In a program, every variable and function will have unique address associated
with it. Whenever a program is loaded into the memory for executing, the
variables and functions and functions will be loaded into their respective
addresses.
Besides the address, every variable will also have size. Same way every function
will occupy some space in the memory.

Direct Addressing:
Whenever any variable is defined, some memory will be allocated for that
variable. The address of this memory is the address of this variable, the value
will be placed in the memory location of its address. This is called direct
addressing.
int a
;/** memory allocated say at address 100**/
a = 12 ;/**a value 12 will be placed in memory starting at address 100**/

Pointer variables and indirect addressing


Pointer variables are used to store addresses. Pointer variables provide indirect
addressing capability.
int *p
;/** memory allocated for p at some address **/
p = 500 ;/** direct addressing: 500 is placed in ps memory **/
*p = 12 ;/**indirect addressing: A value of 12 is placed in memory location,
whose address is present in ps location. That is 500. So 12 will
be placed in memory location 500**/
Indirect addressing is also called pointer dereferencing.
p = 1000;
*p = 12; /This time 12 will be placed in memory location 1000*/
Indirect addressing provides the capability to read from any memory location of
our interest . Which ever memory we would like to read or write, we can
initialize pointer variable p with the address of that memory. Now by
dereferencing the pointer we can read or write into that memory location.

Pointer Dereference:
Putting * in front of a pointer variable and using is called dereferencing.
When you dereference a pointer variable, it represents variable of that
pointer type.
float *pf ;
Compiler understands two things. One is pf is a pointer variable. So
memory space will be allocated to this pointer variable. Second is that when
you dereference this pf pointer it acts as float variables. So *pf is a float
variable.
Dereferencing of pointer variable provides the indirect addressing capability.

Pointer Type:
Every pointer variable will have a type.
int
*pi; // pointer of type integer
short *ps; //pointer of type short
char *pc; //pointer of type char

Using Pointer as array


De-referencing is the power capability of pointers. We can de-reference not only
objects pointed by a pointer, but also the successive objects.
*p
is the object pointed by pointer p.
*(p+1) is the second object from the object pointed by pointer p.
*(p+2) is the second object from the object pointed by pointer p.
The same dereferencing can be achieved by using the pointer as array.
p[0] ; object pointed by pointer p
p[1] ; second object;
p[2] ; third object;

Use of pointers:

For accessing absolute memory of our interest.


Accessing memory belonging to one type as different type.
Pointer provide pass by reference method for functions
Dynamic memory
Linked lists
Function pointers for Call back functions or event handlers

Arrays and Structures


Arrays and structures are the basic building blocks of data structures. Variables can
be classified into scalar, vector and compound. Normal variables char, int float
as of type scalar. The arrays are considered as vector type and structures
represent the compound type.

Arrays:
We use arrays when we need multiple elements of same type. These elements
of an array could be simple variables like chars and integers or could be
structures also. These structures may contain arrays within themselves. In this
way we can build complex data structures with the help of arrays and
structures.
we can get address of any element of array by using & operator &arr[1]
//*address of second element of array *//
&arr[2] //*address of third element of array *//

Passing arrays to functions


As array names represent pointers. We can pass arrays to a function as
pointers.
eg. Void dispMarks(int marks[]);
Void dispMarks(int *marks);
Both the examples can be used to declare a function which takes marks array as
parameter. Notice pointer and arrays can be used interchangeably.

Structures
Organizing the information as structures or objects
It is a good practice to organize all the global data as structures or as array of
structures . It is not good to have so many global scalar variables. Organizing all
the global data into a set of structures provides modularity to the data. In fact
we can equate objects in object oriented programming to structures in C
programming. So similar to how object oriented programmers try to identify the
objects. C programmers should identify the good structures and build functions
to operate on these global structures.
eg: typedef struct
(defining a graphic point structure, which contains
{ int x
x and y c-oordinate fields.)
int y
}point_t;
point_t
point;

Structure pointers
Unlike arrays, name of a structure does not represent a pointer. Structure
variable behave like normal scalar variables (int, char..). So to get the address
of a structure we should use & operator.
sampStruct_t st;
/* structure variable */
sampStruct_t pSt;
/* pointer to structure variable */
pSt

&st;

/* pointer is initialized with the address of st */

The way we represent fields of a structure is different for structure variables and
structure pointers.
st.chVal; /* we use (.) To separate structure variable name and field name*/
st.inVal;
pStchVal; /* we use arrow to access fields through structure pointer */
In fact the above is the shorthand notation for (*pSt).chVal.

Self referential structures


If a structure contains, pointer to itself as an element in the structure, it is called
self referential structure. Self referential structures are the best way of
implementing the linked list. As each node in the linked list point to the identical
node, so node structure should contain a pointer to the same structure.
eg:
typedef struct studentNode_s
{
char
name[20];
int
stuId
int
phoneNum;
struct
studentNode_s
*pNext;
} studentNode_t;

Passing structures to functions as parameters


Structure variables, when passed to a function, they will be passed by value
similar to normal variables. If structure is big, passing it by value is not efficient
mechanism, as entire structure needs to be copied to the stack. Better way to
pass structure is by reference, by using & operator.
eg: displayStructure(st1) ; /* structure is passed by value */
displayStructure(&st1) ; /* structure is passed by reference */

Optimization of Memory needs


1)
2)
3)
4)

5)
6)
7)
8)

Byte arithmetic takes less time than integer arithmetic. So use unsigned
bytes for short and a short for integer if possible, to optimize use of RAM
and ROM available in the system.
Avoid use of library functions in case a generalized function is expected to
take more memory when its coding is simple.
Use modifier register for frequently used variables.
Use global variables if shared data problems are tackled and use static
variables in case frequently saving on the stack is needed. ( when a variable
is declared static, the processor accesses with less instructions than for the
stack).
Whenever feasible combine two function of more or less similar codes.
Reduce use of frequent function calls and nested call and thus reduce the
time and RAM memory needed for the stacks.
Use the delete function when there is no longer a need for a set of
statements after they execute.
If feasible use alternatives to switch statements with a table of pointers to
the functions.

Useful links:
Quality and Standards (automotive):
http://www.misra-c2.com/ (look in resources)

Cosmic compilers:
http://www.cosmic-software.com/download.php

Developing Embedded Software in C:


http://www.ece.utexas.edu/~valvano/embed/toc1.htm

Anda mungkin juga menyukai