0% found this document useful (0 votes)
404 views59 pages

Embedded C and RTOS Unit 1 and 2

The document provides an overview of embedded C and real-time operating systems (RTOS). It discusses two units - the first on embedded C concepts like data types, control structures, pointers and arrays. The second unit covers additional C extensions and libraries. It also introduces RTOS concepts like scheduling, objects, services and commercially available RTOS like VxWorks.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
404 views59 pages

Embedded C and RTOS Unit 1 and 2

The document provides an overview of embedded C and real-time operating systems (RTOS). It discusses two units - the first on embedded C concepts like data types, control structures, pointers and arrays. The second unit covers additional C extensions and libraries. It also introduces RTOS concepts like scheduling, objects, services and commercially available RTOS like VxWorks.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 59

January 9,

EMBEDDED C BY NAGAVELLY SREEDHAR


2018

Embedded ‘C’ and RTOS

Unit 1:

Embedded “C‟: Introduction to ANSI C, Basics of ANSI C, Control Structures:


branching and looping, pointers, arrays, structures, unions, etc. Keil Cx51
Compiler and compiler control directives. Cx51 Language extensions: Keywords,
memory types, memory models.

Unit 2:

Embedded “C‟: Cx51 Language extensions: data types, bit manipulation, etc.
Preprocessor and preprocessor directives.Cx51 Compiler Library reference.

RTOS: Introduction to RTOS: Introduction, What is an RTOS, RTOS


Scheduler, objects, services, Key characteristics of an RTOS. Commercially
available RTOS (PSOS, ThreadX, VXWorks, Nucleus, WinCE), Introduction to
VxWorks.

1
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Introduction to ANSI C

When C was first written the standard was set by its authors Kernighan and
Ritche - hence "K&R C". In 1990, an international ANSI standard for C was
established which differs from K&AMPR C in a number of ways.

o The only really important difference is the use of function prototypes.


o To allow the compiler to check that we are using functions correctly
o ANSI C allows us to include a function prototype which gives the type of
the function and
o The type of each parameter before we define the function.

For example, a prototype for the sum function would be:

int sum(int,int);

meaning sum is an int function which takes two int parameters. The only other
major change is can declare parameter types along with the function as in:

int sum(int a, int b);

rather than:

int sum(a,b)

int a,b;

was used in the original K&R C. Again, it is just a small change. Notice that even
if we are using an ANSI compiler we don't have to use prototypes and the K&R
version of the code will work perfectly well.

Basics of ANSI C

We briefly list some of C's characteristics that define the language and also
have led to its popularity as a programming language. Naturally we will be
studying many of these aspects throughout the course.

• Small size

2
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

• Extensive use of function calls

• Loose typing -- unlike PASCAL

• Structured language

• Low level (Bitwise) programming readily available

• Pointer implementation - extensive use of pointers for memory, array,


structures and functions.

C has now become a widely used professional language for various reasons.

• It has high-level constructs.

• It can handle low-level activities.

• It produces efficient programs.

• It can be compiled on a variety of computers.

A C program basically has the following form:

• Preprocessor Commands

• Type definitions

• Function prototypes -- declare function types and variables passed to


function.

• Variables

• Functions

We must have a main() function.

A function has the form:

type function_name (parameters)

local variables

C Statements

3
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

If the type definition is omitted C assumes that function returns


an integer type. NOTE: This can be a source of problems in a program.

So returning to our first C program:

/* Sample program */

main()

printf( "I Like C \n");

exit ( 0 );

NOTE:

• C requires a semicolon at the end of every statement.

• printf is a standard C function -- called from main.

• \n signifies newline. Formatted output -- more later.

• exit() is also a standard function that causes the program to terminate.


Strictly speaking it is not needed here as it is the last line of main() and
the program will terminate anyway.

Let us look at another printing statement:

printf (".\n.1\n..2\n...3\n");

The output of this would be:

.1

..2

...3

4
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Computers execute instructions one by one, in a strict order set out by the
programmers, this is known as the flow of execution.

o But only having a straight flow would chew up our program memory really
fast, what we need is some loops.
o The two things that make computers useful are the ability to process
data quickly and the ability to use data to influence the program flow.
o C provides a set of flow control statements very much like any other
algebraic language. It gives if, switch, for, and do/while statements. Let’s
look at each one.

Control Structures: branching and looping

IF

5
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

There are two forms of the if statement, one is used to execute a block of code
if a condition is true, the other executes one block of code if a condition is true
and another if the condition is false.

The if statement looks like:

if (condition) {

Statement;

Where the condition is some logical comparison, a bool variable, or function call
that returns a bool. For comparison operators, C has a standard set:

== - equal to. Beware a single = character is assignment.

!= - not equal to.

< - less than

> - greater than

<= - less than or equal to

>= - greater than or equal to

&& - logical AND

6
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

|| - logical OR. This is a pair of vertical bars, or stick characters.

If we wanted to compare some variable to a value, we would write:

if ( answerToQuestion == 42) {

DoSomething();

So far we have seen how an if statement can be used to execute a block of code
if the condition is true, bypassing it if it is false. If we wish to do one thing if
the condition is true and another if it is false, we use the else clause.

if ( temperature < 0) {

printf( “frozen\n”);

} else {

printf( “not frozen\n”);

Within the curly braces of the if statement, we can put any number of
statements, including other if statements. These are called nested ifs.

if ( temperature < 0) {

printf( “frozen\n”);

} else {

if ( temperature < 100) {

printf( “liquid\n”);

} else {

printf( “gas\n”);

7
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

For our next example, assume that we want to classify characters as vowels or
consonants. We could use something like:

#include <iso646.h>

#include <stdio.h>

char letter;

readin( letter); // call a function that will get our character

if ((letter == 'A') or (letter == 'a')) {

printf( "Vowel\n");

} else {

if ((letter == 'E') or (letter == 'e')) {

printf( "Vowel\n");

} else {

if ((letter == 'I') or (letter == 'i')) {

printf( "Vowel\n");

} else {

if ((letter == 'O') or (letter == 'o')) {

printf( "Vowel\n");

} else {

if ((letter == 'U') or (letter == 'u')) {

printf( "Vowel\n");

} else {

if ((letter == 'Y') or (letter == 'y')) {

printf( "Sometimes a vowel\n");

} else {

8
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

printf( "Consonant\n");

SWITCH

What we really need is a multi-way if statement, C has a statement


called switch that provides this. It takes an integer or char as a parameter, and
gives us a multi-way branch. Float values cannot be used as parameters since
they cannot be reliably checked for equivalence. The switch statement looks
like:

switch (var) {

case value1: // note the colon after the case value

statement;

statement;

break;

case value2:

statement;

statement;

break;

default:

statement;

statement;

break;

9
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

1) If we forget to put in our break statement our code will just continue
through the next case until it hits a break in a trailing case or the closing
curly brace of the switch.
2) Falling through cases because of a missing break is a common bug, be
careful to put break statements at the bottom of every case block.
3) Falling through case statements can be useful if you have multiple cases
that are handled identically.

Our example of consonant/ vowel sorting would turn into:

char letter;

10
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

readin( letter); // call a function that will get our character

switch (letter) {

case 'A':

case 'a':

case 'E':

case 'e':

case 'I':

case 'i':

case 'O':

case 'o':

case 'U':

case 'u':

printf( "Vowel\n");

break; // Andrei's style of de-indented breaks

case 'Y':

case 'y':

printf( "Sometimes a vowel\n");

break;

default:

printf( "Consonant\n");

break;

o If the switch value is not handled by any of the explicit cases, it is


handled by the defaultcase.

11
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

o If every possible case is explicitly handled, it is a good idea to put in a


default case to handle all of the impossible cases that can arise from
memory corruption, programming errors, or normal user inputs.

Another option is to just put a comment where the default code would go
explaining why you think the default case is not necessary.

FOR

For statements are used to iterate over, or individually processes each of, a
bunch of data. The intent is to provide a loop with a way to terminate it, and a
variable that we can use in calculations or as an index to an array.

The for statement has confused syntax made up of three parts separated by
semicolons:

for ( initial; test; increment ) {

statements;

• Initial - this section is used to initialize your loop control variable.

• Test - the for loop will continue as long as this test is true.

• Increment - this section is used to alter the value of the control variable,
typically increment or decrement it.
12
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

For example, here we have a very common pattern for going through a C array.
Since C arrays starts at 0, we initialize our counter to 0 and continue the loop
while the counter is less than the number of items in the array.

uint16_t total = 0;

uint16_t values[100];

...

for ( i = 0; i < 100; i++) {

total = total + values[i];

This example goes from 0 to 99, which matches the element numbers of the
array. If we don’t need the various sections of the for statement, they can be
left out. We control variables may already be initialized, or something inside of
your loop may change the control variable.

For embedded systems, we will typically have an infinite loop which can be set up
as:

for ( ; ;) {

DoSomething();

or

while ( true) {

DoSomething();

#define FOREVER for (; ;)

Now our program will have a forever loop:

FOREVER {

Body of the embedded program goes here.

13
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

DO/WHILE:

The while loop executes a block of statements while a condition is true. There
are two forms, one with the test at the top of the loop, and the other with the
test at the bottom.

while ( moreDataToProcess) {

moreDataToProcess = Process( data);

do {

moreDataToProcess = Process( data);

} while (moreDataToProcess);

14
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

The major difference between the two forms is that the do-while will run at
least once. Sometimes, we need that first loop to get things started, like when
reading status registers.

Unlike if, switch, and for loops with counters, there is no easy way to determine
if a while loop will terminate. It seems to be waiting for the data ready bit on a
UART to become active and it never does because of something happened.

A better idea is to add in a loop counter that will

uint32_t counter = 0;

while (( UARTDataReady == 0) and (counter < 1000)) {

counter = counter + 1;

someOtherCommands;

Pointers in C:

The C equivalent of the indirect instruction is the pointer. The register holding
the address to be indirectly accessed in the assembler examples is a normal C
type, except that its purpose is to hold an address rather than a variable or
constant data value.

It is declared by:

15
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

1. unsigned char *pointer0 ;

Note the asterisk prefix, indicating that the data held in this variable is an
address rather than a piece of data that might be used in a calculation etc.

In all cases in the assembler example two distinct operations are required:

1. Place address to be indirectly addressed in a register.

2. Use the appropriate indirect addressing instruction to access data held


at chosen address.

Fortunately in C the same procedure is necessary, although the indirect register


must be explicitly defined, whereas in assembler the register exists in
hardware.

1. /* 1 - Define a variable which will hold an address */

2. unsigned char *pointer ;

3. /* 2 - Load pointer variable with address to be accessed*/

4. /*indirectly */

5. pointer = &c_variable ;

6. /* 3 - Put data '0xff' indirectly into c variable via*/

7. /*pointer */

8. *pointer = 0xff ;

The ability to access data either directly, x = y, or indirectly, x = *y_ptr, is


extremely useful. Here is C example:

1. /* Demonstration Of Using A Pointer */

2. void function(void)

3. {

4. unsigned char c_variable ; // 1 - Declare a c variable unsigned char

5. *ptr ; // 2 - Declare a pointer (not pointing at anything yet!)

16
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

6. c_variable = 0xff ; // 3 - Set variable equal to 0xff directly

7. // OR, to do the same with pointers:

8. ptr = &c_variable ; // 4 - Force pointer to point at c_variable at run


time

9. *ptr = 0xff ; // 5 - Move 0xff into c_variable indirectly

10. }

Arrays:

Uninitialized Arrays

The variables declared via

1. unsigned char x ;

2. unsigned char y ;

are single 8 bit memory locations. The declarations:

1. unsigned int a ;

2. unsigned int b ;

yield four memory locations, two allocated to 'a' and two to 'b'. In other
programming languages it is possible to group similar types together in arrays.
In basic an array is created by DIM a(10).

Likewise 'C' incorporates arrays, declared by:

1. unsigned char a[10] ;

This has the effect of generating ten sequential locations, starting at the
address of 'a'. As there is nothing to the right of the declaration, no initial
values are inserted into the array. It therefore contains zero data and serves
only to reserve ten contiguous bytes.

Initialized Arrays

A more usual instance of arrays would be

1. unsigned char test_array [] = { 0x00,0x40,0x80,0xC0,0xFF } ;

17
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

where the initial values are put in place before the program gets to "main()".
Note that the size of this initialized array is not given in the square brackets -
the compiler works-out the size automatically upon compilation.

Another common instance of an array is analogous to the BASIC string as per:

1. A$ = "HELLO!"

In C this equates to:

1. char test_array[] = { "HELLO!" } ;

In C there is no real distinction between strings and arrays as a C array is just a


series of sequential bytes occupied either by a string or a series of numbers. In
fact the realms of pointers and arrays overlap with strings by virtue of:

1. char test_array = { "HELLO!" } ;

2. char *string_ptr = { "HELLO!" } ;

Case 1 creates a sequence of bytes containing the ASCII equivalent of


"HELLO!". Likewise the second case allocates the same sequence of bytes but in
addition creates a separate pointer called *string_ptr to it. Notice that the
"unsigned char" used previously has become "char", literally an ASCII
character.

The second is really equivalent to:

1. char test_array = { "HELLO!" } ;

Then at run time:

1. char arr_ptr = test_array ; // Array treated as pointer - or;

2. char arr_ptr = &test_array[0] ;

3. // Put address of first element of array into pointer

Stating an array name in this context causes the array to be treated as a


pointer to the first location of the array. Hence no "address of" (&) or '*' to be
seen.

18
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

The second case reads as "get the address of the first element of the array
name and put it into arr_ptr". No implied pointer conversion is employed, just
the return of the address of the array base.

The new pointer "*arr_ptr" now exactly corresponds to *string_ptr, except


that the physical "HELLO!" they point at is at a different address.

Using Arrays

Arrays are typically used like this

1. /* Copy the String HELLO! Into An Empty Array */

2. unsigned char source_array[] = { "HELLO!" } ;

3. unsigned char dest_array[7];

4. unsigned char array_index ;

5. array_index = 0 ; // First character index

6. while(array_index < 7) // Check for end of array

7. {

8. dest_array[array_index] = source_array[array_index] ;

9. // Move character-by-character into destination array

10. array_index++ ; // Next character index

11. }

Structures:

Structures are perhaps what makes C such a powerful language for creating
very complex programs with huge amounts of data. They are basically a way of
grouping together related data items under a single symbolic name.

Why Use Structures?

Here is an example: A piece of C51 software had to perform a linearization


process on the raw signal from a variety of pressure sensors manufactured by
the same company.

19
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

o For each sensor to be catered for there is an input signal with a span and
offset, a temperature coefficient, the signal conditioning amplifier, a
gain and offset.
o The information for each sensor type could be held in "normal" constants
thus:

unsigned char sensor_type1_gain = 0x30 ;

unsigned char sensor_type1_offset = 0x50 ;

unsigned char sensor_type1_temp_coeff = 0x60 ;

unsigned char sensor_type1_span = 0xC4 ;

unsigned char sensor_type1_amp_gain = 0x21 ;

unsigned char sensor_type2_gain = 0x32 ;

unsigned char sensor_type2_offset = 0x56 ;

unsigned char sensor_type2_temp_coeff = 0x56 ;

unsigned char sensor_type2_span = 0xC5 ;

unsigned char sensor_type2_amp_gain = 0x28 ;

unsigned char sensor_type3_gain = 0x20 ;

unsigned char sensor_type3_offset = 0x43 ;

unsigned char sensor_type3_temp_coeff = 0x61 ;

unsigned char sensor_type3_span = 0x89 ;

unsigned char sensor_type3_amp_gain = 0x29 ;

As can be seen, the names conform to an easily identifiable pattern of:

1. unsigned char sensor_typeN_gain = 0x20 ;

2. unsigned char sensor_typeN_offset = 0x43 ;

3. unsigned char sensor_typeN_temp_coeff = 0x61 ;

4. unsigned char sensor_typeN_span = 0x89 ;

20
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

5. unsigned char sensor_typeN_amp_gain = 0x29 ;

Where 'N' is the number of the sensor type. A structure is a neat way of
condensing this type of related and repeating data. In fact the information
needed to describe a sensor can be reduced to a generalized:

1. unsigned char gain ;

2. unsigned char offset ;

3. unsigned char temp_coeff ;

4. unsigned char span ;

5. unsigned char amp_gain ;

Unions: Unions allow you to define different data type references for the same
physical address. This way you can address a 32-bit word as a "long" OR as 2
different "ints" OR as an array of 4 bytes.

A union is similar in concept to a structure except that rather than creating


sequential locations to represent each of the items in the template, it places
each item at the same address. A union specifying 4 bytes may still only occupy
a single byte. A union may consist of a combination of longs, char and ints all
based at the same physical address.

The number of bytes of RAM used by a union is simply determined by the size
of the largest element, so:

1. union test

2. {

3. char x ;

4. int y ;

5. char a[3] ;

6. long z ;

7. } ;

21
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

requires 4 bytes, this being the size of a long. The physical location of each
element is the base address plus the following offsets:

Keil Cx51 Compiler and compiler control Directives:

The C51 Compiler provides a number of directives we may use to control source
file compilation. Directives are composed of one or more letters or digits and,
unless otherwise specified, may be specified after the filename on the command
line or within the source file using #pragma. For example:

C51 testfile.c SYMBOLS CODE DEBUG

or

#pragma SYMBOLS CODE DEBUG

The source file to compile is testfile.c and SYMBOLS, CODE, and DEBUG are
the directives.

Control directives may be divided into three groups:

▪ Source controls define macros on the command line and determine the
name of the file to be compiled.

▪ Object controls affect the form and content of the generated object
module (*.OBJ). These directives allow us to specify the optimizing level
or include debugging information in the object file.

▪ Listing controls govern various aspects of the listing file (*.LST), in


particular its format and specific content.

The following table is an alphabetical list of the control directives available in


the Cx51 Compiler.

22
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Directive Group Description

AREGS Object Enables absolute register (ARn) addressing.

ASM Source Marks the beginning of an inline assembly block.

BROWSE1 Object Enables source browser information.

CODE1 Listing Includes generated assembly in the listing file.

COMPACT1 Object Selects the COMPACT memory model.

COND1 Listing Includes (in the listing file) conditional source lines
skipped by the preprocessor.

DEBUG1 Object Includes debugging information in the object file.

DEFINE2 Source Defines preprocessor names on the command line.

DISABLE Object Disables interrupts for the duration of a single


function.

EJECT Listing Inserts a form feed character into the listing file.

ENDASM Source Marks the end of an inline assembly block.

FARWARNING Object Issue a warning

Cx51 Language extensions

The Cx51 Compiler provides several extensions to ANSI Standard C to support


the elements of the 8051 architecture. These include extensions for:

▪ Memory Areas

▪ Memory Models

▪ Memory Types

▪ Data Types

23
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

▪ Bit Variables and Bit-Addressable Data

▪ Special Function Registers

▪ Pointers

▪ Function Attributes

Keywords

To facilitate many of the features of the 8051, the Cx51 Compiler adds a
number of new keywords to the scope of the C language:

▪ _at_ ▪ far ▪ sbit

▪ alien ▪ idata ▪ sfr

▪ bdata ▪ interrupt ▪ sfr16

▪ bit ▪ large ▪ small

▪ code ▪ pdata ▪ _task_

▪ compact ▪ _priority_ ▪ using

▪ data ▪ reentrant ▪ xdata

Program Memory:

Program (CODE) memory is read only; it cannot be written to. Program memory
may reside within the 8051 MCU, it may be external, or it may be both,
depending upon the 8051 derivative and the hardware design.

o The 8051 architecture supports up to 64K Bytes of program memory.


However, program space can be expanded using code banking.

o Some devices offer a larger code space.

o Program code, including all functions and library routines, is stored in


program memory.

o Constant variables may also be stored in program memory.

o The 8051 executes programs stored in program memory only.

24
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

o Program memory may be accessed from your C programs using the code
memory type specifier.

Internal Data Memory:

Internal data memory resides within the 8051 MCU and is read/write. Up to
256 bytes of internal data memory are available depending upon the 8051
derivative. The first 128 bytes of internal data memory are both directly and
indirectly addressable. The upper 128 bytes of data memory (from 0x80 to
0xFF) can be addressed only indirectly. There is also a 16 byte area starting at
20h that is bit-addressable.

Access to internal data memory is very fast because it can be accessed using an
8-bit address. However, internal data memory is limited to a maximum of 256
bytes.

Internal data can be broken down into three distinct memory types: data,
idata, and bdata.

o The data memory specifier always refers to the first 128 bytes of
internal data memory. Variables stored here are accessed using direct
addressing.

o The idata memory specifier refers to all 256 bytes of internal data
memory; however, this memory type specifier code is generated by
indirect addressing which is slower than direct addressing.

o The bdata memory specifier refers to the 16 bytes of bit-addressable


memory in the internal data area (20h to 2Fh). This memory type
specifier allows you to declare data types that may also be accessed at
the bit level.

Memory Types:

The Cx51 Compiler provides access to all 8051 memory areas. Variables may be
explicitly assigned to a specific memory space (by including a memory type
specifier in the declaration) or implicitly assigned (based on the memory model).

The following table summarizes the memory type specifiers

25
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Memory
Type Description

code Program memory (64 KBytes); accessed by opcode MOVC


@A+DPTR.

data Directly addressable internal data memory; fastest access to


variables (128 bytes).

idata Indirectly addressable internal data memory; accessed across


the full internal address space (256 bytes).

bdata Bit-addressable internal data memory; supports mixed bit and


byte access (16 bytes).

xdata External data memory (64 KBytes); accessed by opcode MOVX


@DPTR.

far Extended RAM and ROM memory spaces (up to 16MB); accessed
by user defined routines or specific chip extensions (Philips
80C51MX, Dallas 390).

pdata Paged (256 bytes) external data memory; accessed by opcode


MOVX @Rn.

As with the signed and unsigned attributes, we may include memory type
specifiers in the variable declaration. For example:

char data var1;

char code text[] = "ENTER PARAMETER:";

unsigned long xdata array[100];

float idata x,y,z;

unsigned int pdata dimension;

unsigned char xdata vector[10][4][4];

char bdata flags;

26
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

▪ For compatibility with previous versions of the C51 Compiler, It may


specify the memory area before the data type. For example, the following
two declarations are equivalent:

▪ data char x; // Old-Style Memory Type Declaration

▪ char data x; // New-Style Memory Type Declaration

Be careful when using the old C51 syntax with memory-specific pointers. For
example, the following two declarations are equivalent:

data char *x; // Old-Style Memory Type Declaration

char *data x; // New-Style Memory Type Declaration

▪ Accessing the internal data memory is considerably faster than accessing


the external data memory. For this reason, place frequently used
variables in internal data memory. Place larger, less frequently used
variables in external data memory.

If no memory type is specified for a variable, the compiler implicitly locates the
variable in the default memory space determined by the memory
model: SMALL, COMPACT, or LARGE. Function arguments and automatic
variables that cannot be located in registers are also stored in the default
memory area.

bdata

The bdata memory type may be used to declare variables only. It may not
declare bdata functions. This memory is directly accessed using 8-bit addresses
and is the on-chip bit-addressable RAM of the 8051. Variables declared with
the bdata type are bit-addressable and may be read and written using bit
instructions.

Variables declared bdata are located in the BDATA memory class.

Declare bdata variables as follows:

unsigned char bdata bdata_var;

Code: The code memory type may be used for constants and functions. This
memory is accessed using 16-bit addresses and may be on-chip or external.

27
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

For constants (ROM variables), code memory is limited to 64K. Objects are
limited to 64K and may not cross a 64K boundary. Constant variables declared
code are located in the CODE memory class.

For program code (functions), code memory is limited to 64K. Program functions
are stored in the CODE memory class by default. The code memory type
specifier is not required.

Declare code objects as follows:

unsigned char code code_constant;

unsigned int func (void)

return (0);

data

The data memory type may be used to declare variables only. It may not
declare data functions. This memory is directly accessed using 8-bit addresses
and is the on-chip RAM of the 8051. It has the shortest (fastest) access time
but the amount of data is limited in size (to 128 bytes or less).

Variables declared data are located in the DATA memory class.

Declare data variables as follows:

unsigned char data fast_variable;

idata

The idata memory type may be used to declare variables only. It may not
declare idata functions. This memory is indirectly accessed using 8-bit
addresses and is the on-chip RAM of the 8051. The amount of idata is limited in
size (to 256 bytes or less). The lower addresses of idata overlap the
corresponding addresses of data memory.

Variables declared idata are located in the IDATA memory class.

28
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Declare idata variables as follows:

unsigned char idata variable;

pdata

The pdata memory type may be used to declare variables only. It may not
declare pdata functions. This memory is indirectly accessed using 8-bit
addresses and is one 256-byte page of external data RAM of the 8051. The
amount of pdata is limited in size (to 256 bytes).

Variables declared pdata are located in the PDATA memory class.

Declare pdata variables as follows:

unsigned char pdata variable;

xdata

The xdata memory type may be used to declare variables only. It may not
declare xdata functions. This memory is indirectly accessed using 16-bit
addresses and is the external data RAM of the 8051. The amount of xdata is
limited in size (to 64K or less).

Variables declared xdata are located in the XDATA memory class.

Declare xdata variables as follows:

unsigned char xdata variable;

Memory Models

The memory model determines the default memory type to use for function
arguments, automatic variables, and declarations that include no explicit
memory type. The C51 Compiler provides three memory models:

▪ Small Model

▪ Compact Model

▪ Large Model

The following table lists the default memory areas used for each memory model.

29
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Parameters & Default Default Default Default


Memory Automatic Global Constant Pointer Pointer
Model Variables Variables Variables Definitions Size

SMALL data data data * 3 bytes

COMPACT pdata pdata pdata * 3 bytes

LARGE xdata xdata xdata * 3 bytes

We may specify the memory model on the compiler command line using
the SMALL, COMPACT, or LARGE directives or using #pragma in the source
file.

Note

▪ Except in very special select applications, the SMALL memory model


generates the fastest, most efficient code.

Small Model

In this model, all variables, by default, reside in the internal data memory of
the 8051 system—as if they were declared explicitly using the data memory
type specifier.

In this memory model, variable access is very efficient. However, all objects
(that are not explicitly located in another memory area) and the stack must fit
into the internal RAM. Stack size is critical because the stack space used
depends on the nesting depth of the various functions.

Compact Model

Using the compact model, by default, all variables reside in a single page
of external data memory of the 8051 system—as if they were explicitly
declared using the pdata memory type specifier.

o This memory model can accommodate a maximum of 256 bytes of


variables. The limitation is due to the addressing scheme used which is
indirect through registers R0 and R1 (@R0, @R1).

30
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

o This memory model is not as efficient as the small model and variable
access is not as fast. However, the compact model is faster than
the large model.

When using the compact model, the C51 Compiler accesses external memory
with instructions that use the @R0 and @R1 operands. R0 and R1 are byte
registers and provide only the low-order byte of the address.

Large Model

In the large model, all variables, by default, reside in external data


memory (which may be up to 64K Bytes). The data pointer (DPTR) is used to
address external memory. This type of data access mechanism generates more
code than the small model or compact model.

DATA TYPES

Data Types: The Cx51 Compiler provides several basic data types we may use in
our C programs. The compiler supports the standard C data types as well as
several data types that are unique to the Cx51 platform.

Data Types Bits Bytes Value Range

bit 1 0 to 1

signed char 8 1 -128 — +127

unsigned char 8 1 0 — 255

enum 8 / 16 1 or 2 -128 — +127 or -32768 — +32767

signed short int 16 2 -32768 — +32767

unsigned short int 16 2 0 — 65535

signed int 16 2 -32768 — +32767

unsigned int 16 2 0 — 65535

signed long int 32 4 -2147483648 — +2147483647

31
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

unsigned long int 32 4 0 — 4294967295

float 32 4 ±1.175494E-38 — ±3.402823E+38

double 32 4 ±1.175494E-38 — ±3.402823E+38

sbit 1 0 or 1

sfr 8 1 0 — 255

sfr16 16 2 0 — 65535

Note

▪ The bit, sbit, sfr, and sfr16 data types are not provided in ANSI C. They
are unique to the Cx51 Compiler.

Special Function Registers

The 8051 family of microcontrollers provides a distinct memory area for


accessing Special Function Registers (SFRs). SFRs are used in our program to
control timers, counters, serial I/Os, port I/Os, and peripherals. SFRs reside
from address 0x80 to 0xFF and can be accessed as bits, bytes, and words.

Within the 8051 family, the number and type of SFRs vary. The C51 Compiler
provides a number of include files for various 8051 derivatives.

Each file contains declarations for the SFRs available on that derivative. The
Cx51 Compiler provides access to SFRs with the sfr, sfr16, and sbit data types.

sfr: The sfr type defines a special function register (SFR). It is used as
follows:

sfr name = address;

Where

name is the name of the SFR.

address is the address of the SFR.

SFRs are declared in the same fashion as other C variables. The only difference
is that the type specified is sfr rather than char or int. For example:
32
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

sfr P0 = 0x80; /* Port-0, address 80h */

sfr P1 = 0x90; /* Port-1, address 90h */

sfr P2 = 0xA0; /* Port-2, address 0A0h */

sfr P3 = 0xB0; /* Port-3, address 0B0h */

P0, P1, P2, and P3 are the SFR name declarations. Names for sfr variables are
defined just like other C variable declarations. Any symbolic name may be used
in a sfr declaration.

The address specification after the equal sign ('=') must be a numeric constant.
Expressions with operators are not allowed. Classic 8051 devices support the
SFR address range 0x80-0xFF.

Note: sfr variables may not be declared inside a function. They must be
declared outside of the function body.

sfr variables are always volatile. The compiler will not optimize accesses to this
type of variables.

sfr16

The sfr16 type defines a 16-bit special function register (SFR). It is used as
follows:

sfr16 name = address;

Where

name is the name of the 16-bit SFR.

address is the address of the 16-bit SFR.

Some 8051 derivatives have 16-bit SFRs there are created using consecutive
addresses in SFR memory to specify 16-bit values. For example, the 8052 uses
addresses 0xCC and 0xCD for the low and high bytes of timer/counter 2
respectively. The Cx51 Compiler provides the sfr16 data type to access two 8-
bit SFRs as a single 16-bit SFR.

Access to 16-bit SFRs using sfr16 is possible only when the low byte
immediately precedes the high byte (little endian) and when the low byte is

33
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

written last. The low byte is used as the address in the sfr16 declaration. For
example:

sfr16 T2 = 0xCC; /* Timer 2: T2L 0CCh, T2H 0CDh */

sfr16 RCAP2 = 0xCA; /* RCAP2L 0CAh, RCAP2H 0CBh */

In this example, T2 and RCAP2 are declared as 16-bit special function registers.

The sfr16 declarations follow the same rules as outlined for sfr declarations.
Any symbolic name can be used in a sfr16 declaration. The address specification
after the equal sign ('=') must be a numeric constant. Expressions with
operators are not allowed. The address must be the low byte of the SFR low-
byte, high-byte pair.

Note: Most 16-bit SFRs are composed of two consecutive addresses that
contain the MSB and LSB (in either order) of the 16-bit register. Depending on
the device, writing to the MSB or LSB "latches" the 16-bit value into the
register. There are four possible configurations for a 16-bit SFR. They are:

LSB Read First, LSB Written Last (sfr16 does this).

MSB Read First, LSB Written Last.

LSB Read First, MSB Written Last.

MSB Read First, MSB Written Last.

The sfr16 keyword may be used to define some (but not all) 16-bit Special
Function Registers. The two SFRs accessed by sfr16 must be organized so that
the low byte is stored in memory first (or at the lower address). For example, if
the LSB is at SFR address 0x9E and the MSB is at SFR address 0x9F, sfr16
may be used.

When we write to a srf16, the code generated by the Keil Cx51 Compiler writes
to the high byte first and then the low byte. In many cases, this is not the
desired order.

If the order in which the bytes are written is important (this is the case with
MANY 8051 devices) we must use the sfr keyword to define and access the
SFRs one byte at a time.

34
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

sfr16 variables may not be declared inside a function. They must be declared
outside of the function body.

sbit

The sbit type defines a bit within a special function register (SFR). It is used in
one of the following ways:

sbit name = sfr-name ^ bit-position;

sbit name = sfr-address ^ bit-position;

sbit name = sbit-address;

Where

name is the name of the SFR bit.

sfr-name is the name of a previously-defined SFR.

bit-position is the position of the bit within the SFR.

sfr-address is the address of an SFR.

sbit-address is the address of the SFR bit.

With typical 8051 applications, it is often necessary to access individual bits


within an SFR. The sbit type provides access to bit-addressable SFRs and other
bit-addressable objects. For example:

sbit EA = 0xAF;

This declaration defines EA as the SFR bit at address 0xAF. On the 8051, this
is the enable all bit in the interrupt enable register.

Note

▪ Storage of objects accessed using sbit is assumed to be little endian


(LSB first). This is the storage format of the sfr16 type but it is
opposite to the storage of int and long data types. Care must be taken
when using sbit to access bits within standard data types.

35
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Any symbolic name can be used in an sbit declaration. The expression to the
right of the equal sign ('=') specifies an absolute bit address for the symbolic
name. There are three variants for specifying the address:

Variant 1

sbit name = sfr-name ^ bit-position;

The previously declared SFR (sfr-name) is the base address for the sbit. It
must be evenly divisible by 8. The bit-position (which must be a number from 0-
7) follows the carat symbol ('^') and specifies the bit position to access. For
example:

sfr PSW = 0xD0;

sfr IE = 0xA8;

sbit OV = PSW^2;

sbit CY = PSW^7;

sbit EA = IE^7;

Variant 2

sbit name = sfr-address ^ bit-position;

A character constant (sfr-address) specifies the base address for the sbit. It
must be evenly divisible by 8. The bit-position (which must be a number from 0-
7) follows the carat symbol ('^') and specifies the bit position to access. For
example:

sbit OV = 0xD0^2;

sbit CY = 0xD0^7;

sbit EA = 0xA8^7;

Variant 3

sbit name = sbit-address;

A character constant (sbit-address) specifies the address of the sbit. It must


be a value from 0x80-0xFF. For example:

36
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

sbit OV = 0xD2;

sbit CY = 0xD7;

sbit EA = 0xAF;

Note

▪ Not all SFRs are bit-addressable. Only those SFRs whose address is
evenly divisible by 8 are bit-addressable. The lower nibble of the SFR's
address must be 0 or 8.

▪ Special function bits represent an independent declaration class that may


not be interchangeable with other bit declarations or bit fields.

▪ The sbit data type declaration may be used to access individual bits of
variables declared with the bdata memory type specifier.

▪ sbit variables may not be declared inside a function. They must be


declared outside of the function body.

Bit-Addressable Objects

Bit-addressable objects are objects that may be addressed as words or as bits.


Only data objects that occupy the bit-addressable area of the 8051 internal
memory fall into this category.

The Cx51 Compiler places variables declared with the bdata memory type into
the bit-addressable area. Furthermore, variables declared with
the bdata memory type must be global (declared outside the scope of a
function). It may declare these variables as shown below:

int bdata ibase; /* Bit-addressable int */

char bdata bary [4]; /* Bit-addressable array */

The variables ibase and bary are bit-addressable. Therefore, the individual bits
of these variables may be directly accessed and modified. Use the sbit keyword
to declare new variables that access the bits of bdatavariables. For example:

sbit mybit0 = ibase ^ 0; /* bit 0 of ibase */

sbit mybit15 = ibase ^ 15; /* bit 15 of ibase */

37
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

sbit Ary07 = bary[0] ^ 7; /* bit 7 of bary[0] */

sbit Ary37 = bary[3] ^ 7; /* bit 7 of bary[3] */

The above example represents declarations, not assignments to the bits of the
ibase and bary bdata variables. The expression following the carat symbol ('^')
in the example specifies the position of the bit to access with this declaration.
This expression must be a constant value.

The range depends on the type of the base variable included in the declaration.
The range is:

▪ 0-7 for char and unsigned char, 0-15 for int, unsigned int,

▪ short, and unsigned short, and

▪ 0-31 for long and unsigned long.

Note

▪ The C51 Compiler assumes that objects accessed using sbit declarations
are stored in little endian byte order. This is the case for sfr16 types.
However, standard C types like int and long are stored in big endian.

We may provide external variable declarations for the sbit type to access these
types in other modules. For example:

extern bit mybit0; /* bit 0 of ibase */

extern bit mybit15; /* bit 15 of ibase */

extern bit Ary07; /* bit 7 of bary[0] */

extern bit Ary37; /* bit 7 of bary[3] */

Declarations involving the sbit type require that the base object be declared
with the memory type bdata. The only exceptions are the variants for special
function bits. The following example shows how to change the ibase and bary
bits using the above declarations.

Ary37 = 0; /* clear bit 7 in bary[3] */

bary[3] = 'a'; /* Byte addressing */

38
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

ibase = -1; /* Word addressing */

mybit15 = 1; /* set bit 15 in ibase */

The bdata memory type is handled like the data memory type except that
variables declared with bdata reside in the bit-addressable portion of the
internal data memory. Note that the total size of this area of memory may not
exceed 16 bytes.

In addition to declaring sbit variables for scalar types, it may also declare sbit
variables for structures and unions. For example:

union lft

float mf;

long ml;

};

bdata struct bad

char m1;

union lft u;

} tcp;

sbit tcpf31 = tcp.u.ml ^ 31; /* bit 31 of float */

sbit tcpm10 = tcp.m1 ^ 0;

sbit tcpm17 = tcp.m1 ^ 7;

Note: We may not specify bit variables for the bit positions of a float.
However, you may include the float and along in a union and then declare bit
variables to access the bits in the long type.

39
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

▪ The sbit data type uses the specified variable as a base address and adds
the bit position to obtain a physical bit address. Physical bit addresses
are not equivalent to logical bit positions for certain data types.

▪ Physical bit position 0 refers to bit position 0 of the first byte. Physical
bit position 8 refers to bit position 0 of the second byte. Because int
variables are stored high-byte first, bit 0 of the integer is located in bit
position 0 of the second byte. This is physical bit position 8 when
accessed using a sbit data type.

bit

The bit type defines a single-bit variable. It is used as follows:

bit name <[>= value<]>;

Where

name is the name of the bit variable.

value is the value to assign to the bit.

The bit type may be used for variable declarations, argument lists, and
function-return values. A bit variable is declared like other C data types. For
example:

static bit done_flag = 0; /* bit variable */

bit testfunc ( /* bit function */

bit flag1, /* bit arguments */

bit flag2)

return (0); /* bit return value */

40
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

All bit variables are stored in a bit segment located in the internal memory area
of the 8051. Because this area is only 16 bytes long, a maximum of 128 bit
variables may be declared within any one scope.

Memory types may be included in the declaration of a bit variable. However,


because bit variables are stored in the internal data area of the 8051, data and
idata memory types only may be included in the declaration. Any other memory
types are invalid.

The following restrictions apply to bit variables and bit declarations:

A bit cannot be declared as a pointer. For example:

bit *ptr; /* invalid */

An array of type bit is invalid. For example:

bit ware [5]; /* invalid */

Functions that disable interrupts (#pragma disable) and functions that are
declared using an explicit register bank (using n) cannot return a bit value. The
Cx51 Compiler generates an error message for functions of this type that
attempt to return a bit type.

Standard C Types

The C Language supports a number of standard data types you may use to define
program variables.

▪ void

▪ char

▪ int

▪ enum

▪ float

▪ double

41
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

void: The void data type defines functions with no return value, functions with
no argument list, and pointers to objects of an undefined type. It is used as
follows:

void function-name (argument-list);

return-type function-name (void);

void *name;

Where

function-name is the name of the a function.

argument-list is the arguments passed to the function.

return-type is the return type of the function.

name is the pointer to the void object(s).

char

The char data type defines a 1-byte binary integer. It is used as follows:

<[>{signed|unsigned}<]> char name <[>= value<]>;

Where

name is the name of the variable.

value is the value to assign to the variable.

int

The int data type defines a binary integer. It is used as follows:

<[>{signed|unsigned}<]> <[>{long|short}<]> int name <[>= value<]>;

enum

The enum keyword defines set of constants of type char or type int depending
on the range of values of the set. It is used as follows:

42
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

enum <[>tag<]> {name <[>= value<]>, ...};

Where

tag is the name of the enum set.

name is the name of an enum constant.

value is the value to assign to the constant. If the value is missing, then it is
assumed to be the value of the previous constant in the set + 1. The
default value for the first constant in the list is 0.

float

The float data type defines an IEEE-754 floating-point number. It is used as


follows:

float name <[>= value<]>;

Where

name is the name of the variable.

value is the value to assign to the variable.

double

The double data type defines an IEEE-754 floating-point number. It is used as


follows:

double name <[>= value<]>;

Where

name is the name of the variable.

value is the value to assign to the variable.

43
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Preprocessor

The preprocessor built into the Cx51 Compiler processes the source text of a
source file before it is actually compiled into machine language and object code.
Preprocessing is the first action the compiler performs. The preprocessor's
purpose is to replace or insert additional text into the source file just prior to
compilation.

Most of the preprocessor functions require activation by including Preprocessor


Directives in our source code. However, the preprocessor always:

▪ Replaces each C comment by a single space.

▪ Removes line continuations (indicated by a backslash ('\') at the end of a


line) are removed and joins the lines broken apart for compilation.

▪ Replaces predefined macro names with their predefined text.

In addition to these operations, the preprocessor in the Cx51 Compiler supports


the more obvious preprocessor operations:

▪ Header Files,

▪ Macros,

▪ Conditional Compilation.

Header Files

Header files or include files are included and processed by the preprocessor.
They provide us with a convenient way to publish global variables, function
prototypes, manifest constants, and macro definitions that are used throughout
a large development effort.

The #include directive specifies the name of the header file to include.

How #include Works

The #include directive tells the C preprocessor to include the contents of the
file specified in the input stream to the compiler and then continue with the
rest of the original file.

For example, the header file, file.h contains the following:

44
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

char *func (void);

The program file, myprog.c includes this header file:

#include "file.h"

void main (void)

while (1)

printf (func());

The output generated by the preprocessor is:

char *func (void);

void main (void)

while (1)

printf (func());

Header files typically contain variable and function declarations along with
macro definitions. But, they are not limited to only those. A header file may
contain any valid C program fragment.

Macros

Perhaps the most useful aspect of the C preprocessor is the ability to create
and use macros. Macros enable you to assign short names to source code blocks.
45
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

o When we use the macro name in your source file, the preprocessor
replaces it with the source code block specified in the macro definition.

A macro definition includes the name of the macro, the macro body, and may
include macro arguments.

▪ Simple Macros require no arguments and are the simplest macros to


define.

▪ Complex Macros accept one or more arguments and may be used like
functions.

▪ Macro Operators lists special macro operators that may be used in macro
definitions.

▪ Predefined Macros lists macros that are defined by the compiler at


compile-time.

Conditional Compilation

Conditional compilation directives allow us to designate parts of the program for


the compiler to ignore. These parts are not compiled and no object code is
generated for them.

Conditional directives resemble the C if statement and may be used to test


defined macros or arithmetic expressions. The test performed by a conditional
directive is done at compile-time by the compiler. Depending on the result of
the test, source code is either included or excluded from the compilation.

There are several reasons to use conditional compilation in our program.

o Our program may require different code depending on which device or


architecture we use. In some cases, library routines may exist in one
configuration that does not exist in another.
o Conditional compilation allows us to handle such a situation by substituting
alternate functions with the unavailable library routines.

Many complex functions require comprehensive test code to verify or validate


I/O and proper operation. Intermediate values may be tested and output as
well. After verification, we may wish to retain the test cases for future
reference. We can include them in conditional blocks we control with a macro.

46
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Preprocessor Directives:

Preprocessor directives must be the first non-whitespace text specified on a


line. All directives are prefixed with the pound or number-sign character ('#').
For example:

#pragma PRINT

#include <stdio.h>

#define DEBUG 1

Whitespace is allowed before and after the number-sign ('#'). A number-sign


('#') that appears alone on a line is interpreted as a null preprocessor directive.
For example:

# define myfavnum 45

# include <stdio.h>

The entire proprocessor directive must be contained in a single source line. Line
continuations, backslash ('\') followed by a new-line character, may be used in
preprocessor directives since these are removed by the preprocessor. For
example:

#define mycode \

{ \

volatile unsigned char i; \

for (i=0; i<100; i++); \

The number-sign ('#') and the preprocessor directive must be explicitly


specified and may not come from a macro expansion. For example:

#define mydef #define otherdef 16

47
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

mydef

In this case, mydef is expanded to define otherdef 16 since # is interpreted


as a stringize operator. This expansion is processed by the compiler and a
syntax error is generated.

The following table lists the preprocessor directives and gives a brief
description of each.

Directive Description

#define Defines a preprocessor macro or constant.

#elif Initiates an alternative branch of the if condition, when the


previous #if, #ifdef, #ifndef, or#elif branch was not taken.

#else Initiates an alternative branch when the previous #if, #ifdef,


or #ifndef branch was not taken.

#endif Ends a #if, #ifdef, #ifndef, #elif, or #else block.

#error Outputs an error message defined by the user.

#if Evaluates an expression for conditional compilation.

#ifdef Evaluates an expression for conditional compilation. The argument


to be evaluated is the name of a definition.

#ifndef Same as #ifdef but the evaluation succeeds if the definition is not
defined.

#include Reads source text from an external file. The notation sequence
determines the search sequence of the included files. The compiler
searches for include files specified with less-than/greater-than
symbols ('<', '>') in the include file directory. Include files specified
with double-quotes (" ") are searched for in the current directory.

#line Specifies a line number and an optional filename. This specification


is used in error messages to identify the error position. For line
synchronization with debug information or the listing file, this

48
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

preprocessor directive must be combined with


the NOPREPROCESS Compiler directive.

#message Outputs an information message defined by the user.

#pragma Allows you to specify directives that may be included on the


compiler command line. Pragmas may contain the same directives
that are specified on the command line.

#undef Deletes a preprocessor macro or constant definition.

#warning Outputs a warning message defined by the user.

Library Reference

The C51 run-time library provides more than 100 predefined functions and
macros you may use in your 8051 C programs. The library makes embedded
software development easier by providing routines that perform common
programming tasks such as string and buffer manipulation, data conversion, and
floating-point math operations.

o The routines in this library conform to the ANSI C Standard. However,


some functions differ slightly in order to take advantage of the features
of the 8051 architecture.
o For example, the isdigit function returns a bit value as opposed to an int.
Where possible, function return types and argument types are adjusted
to use the smallest data type possible.
o Moreover, unsigned data types are favored over signed types. These
changes to the standard library provide a maximum of performance while
reducing program size.

All routines in this library are implemented to be independent of register bank.

The C51 library includes six different compile-time libraries which are
optimized for various functional requirements. These libraries support most of
the ANSI C function calls.

49
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Library File Description

C51S.LIB Small model library without floating-point arithmetic.

C51FPS.LIB Small model floating-point arithmetic library.

C51C.LIB Compact model library without floating-point arithmetic.

C51FPC.LIB Compact model floating-point arithmetic library.

C51L.LIB Large model library without floating-point arithmetic.

C51FPL.LIB Large model floating-point arithmetic library.

80C751.LIB Library for the Philips 750/751/752 derivatives.

Programs written for the Philips 80C51MX, Dallas 390 Contiguous Mode, and
Variable Code Banking require a different set of C51 run-time libraries. The
linker automatically includes the correct library.

The following library sets are available in the PK51 Professional Developer's Kit.
Each library set consists of six different library files as explained in the
previous table.

Library Set Description

C51*.LIB Standard library set of Classic 8051.

C51B*.LIB Library set for Classic 8051 with far memory type (variable
banking) support.

C51M*.LIB Library set for Mentor M8051EW on-chip program code banking
(IBANKING).

C51N*.LIB Library set for Mentor M8051EW with far memory type support
and on-chip program code banking (IBANKING).

CD51*.LIB Library set for Dallas Contiguous Mode.

50
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

CX51*.LIB Library set for Philips 80C51MX for ROM(LARGE),


ROM(COMPACT), and ROM(SMALL).

CH51*.LIB Library set for Philips 80C51MX without ECRM mode and
ROM(HUGE).

CS51*.LIB Library set for Philips 80C51MX with ECRM mode and
ROM(HUGE).

Source code is provided (in the \KEIL\C51\LIB folder) for the following library
routines:

Library Routine Filename Description

calloc calloc.c Allocates memory for an array of elements.

free free.c Releases memory allocated with calloc, malloc,


or realloc.

_getkey getkey.c Waits for a character to be received from the


standard 8051 serial port.

init_mempool init_mem.c Initializes the memory pool used by


the calloc, free, malloc, and realloc routines.

malloc malloc.c Allocates a memory block from the memory pool.

putchar putchar.c Transmits a character using the standard 8051


serial port.

realloc realloc.c Changes the size of a previously allocated memory


block.

51
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

RTOS: Introduction to RTOS: Introduction

A real-time operating system (RTOS) is key to many embedded systems today


and, provides a software platform upon which to build applications. Not all
embedded systems, however, are designed with an RTOS. Some embedded
systems with relatively simple hardware or a small amount of software
application code might not require an RTOS.

What is an RTOS?

A real-time operating system (RTOS) is a program that schedules execution in a


timely manner, manages system resources, and provides a consistent foundation
for developing application code. Application code designed on an RTOS can be
quite diverse, ranging from a simple application for a digital stopwatch to a much
more complex application for aircraft navigation.

Every RTOS has a kernel. On the other hand, an RTOS can be a combination of
various modules, including the kernel, a file system, networking protocol stacks,
and other components required for a particular application, as illustrated at a
high level in Figure 2.1.

Figure 2.1: High-level view of an RTOS, its kernel


52
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Figure 2.2: Common components in an RTOS kernel that including objects

The scheduler is at the heart of every kernel. A scheduler provides the


algorithms needed to determine which task executes when.

• Schedulable entities,
• multitasking,
• context switching,
• dispatcher, and
• scheduling algorithms

Schedulable Entities: A schedulable entity is a kernel object that can compete


for execution time on a system, based on a predefined scheduling algorithm.
Tasks and processes are all examples of schedulable entities found in most
kernels.

Multitasking: Multitasking is the ability of the operating system to handle


multiple activities within set deadlines. A real-time kernel might have multiple
tasks that it has to schedule to run. One such multitasking scenario is
illustrated in Figure 2.3.

53
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Figure 2.3: Multitasking using a context switch

The Context Switch

Each task has its own context, which is the state of the CPU registers required
each time it is scheduled to run.

1) A context switch occurs when the scheduler switches from one task to
another.
2) Every time a new task is created, the kernel also creates and maintains an
associated task control block (TCB).
3) TCBs contain everything a kernel needs to know about a particular task.
When a task is running, its context is highly dynamic.
4) This dynamic context is maintained in the TCB. When the task is not
running, its context is frozen within the TCB, to be restored the next
time the task runs.

A typical context switch scenario is illustrated in Figure 2.3.

As shown in Figure 2.3, when the kernel s scheduler determines that it needs to
stop running task 1 and start running task 2, it takes the following steps:

1. The kernel saves task 1 s context information in its TCB.

2 It loads task 2 s context information from its TCB, which becomes the
current thread of execution.

54
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

3. The context of task 1 is frozen while task 2 executes, but if the scheduler
needs to run task 1 again, task 1 continues from where it left off just before
the context switch.

The Dispatcher

The dispatcher is the part of the scheduler that performs context switching
and changes the flow of execution.

At any time an RTOS is running, the flow of execution, also known as flow of
control, is passing through one of three areas: through an application task,
through an ISR, or through the kernel.

• When a task or ISR makes a system call, the flow of control passes to
the kernel to execute one of the system routines provided by the kernel.
• When it is time to leave the kernel, the dispatcher is responsible for
passing control to one of the tasks in the user s application.

Scheduling Algorithms

As mentioned earlier, the scheduler determines which task runs by following a


scheduling algorithm. Most kernels today support two common scheduling
algorithms:

• Preemptive priority-based scheduling, and

• Round-robin scheduling.

Preemptive Priority-Based Scheduling

As shown in Figure 2.4 with this type of scheduling, the task that gets to run at
any point is the task with the highest priority among all other tasks ready to run
in the system.

55
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Figure 2.4: Preemptive priority-based scheduling

Real-time kernels generally support 256 priority levels, in which 0 is the highest
and 255 the lowest. Some kernels appoint the priorities in reverse order, where
255 is the highest and 0 the lowest.

1) With a preemptive priority-based scheduler, each task has a priority, and


the highest-priority task runs first.
2) If a task with a priority higher than the current task becomes ready to
run, the kernel immediately saves the current task s context in its TCB
and switches to the higher-priority task.
3) As shown in Figure 2.4 task 1 is preempted by higher-priority task 2,
which is then preempted by task 3.
4) When task 3 completes, task 2 resumes; likewise, when task 2 completes,
task 1 resumes.

Round-Robin Scheduling

Round-robin scheduling provides each task an equal share of the CPU execution
time. Pure round-robin scheduling cannot satisfy real-time system requirements
because in real-time systems, tasks perform work of varying degrees of
importance.

Instead, preemptive, priority-based scheduling can be augmented with round-


robin scheduling which uses time slicing to achieve equal allocation of the CPU
for tasks of the same priority as shown in Figure 2.5.

56
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Figure 2.5: Round-robin and preemptive scheduling

With time slicing, each task executes for a defined interval, or time slice, in an
ongoing cycle, which is the round robin.

1) A run-time counter tracks the time slice for each task, incrementing on
every clock tick. When one task s time slice completes, the counter is
cleared, and the task is placed at the end of the cycle.
2) Newly added tasks of the same priority are placed at the end of the
cycle, with their run-time counters initialized to 0.
3) If a task in a round-robin cycle is preempted by a higher-priority task, its
run-time count is saved and then restored when the interrupted task is
again eligible for execution.
4) This idea is illustrated in Figure 2.5, in which task 1 is preempted by a
higher-priority task 4 but resumes where it left off when task 4
completes.

Key Characteristics of an RTOS

An application's requirements define the requirements of its underlying RTOS.


Some of the more common attributes are

•reliability,
•predictability,
•performance,
•compactness, and
•scalability.

57
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

Commercial available RTOSes

Commercial RTOSes different from traditional OS gives more predictability


used in the following areas such as:

Embedded Systems or Industrial Control Systems

Parallel and Distributed Systems

E.g. LynxOS, VxWorks, pSoS, QNX, bluecat

Traditionally these systems can be classified into a Uniprocessor,


Multiprocessor or Distributed Real-Time OS

POSIX: POSIX stands for Portable Operating System Interface. “X” has been
suffixed to the abbreviation to make it sound Unix-like. Over the last decade,
POSIX has become an important standard in the operating systems area
including real-time operating systems. The importance of POSIX can be gauzed
from the fact that nowadays it has become uncommon to come across a
commercial operating system that is not POSIX-compliant. POSIX started as an
open software initiative.

Windows CE: Windows CE is a stripped down version of Windows, and has a


minimum footprint of 400KBytes only. It provides 256 priority levels. To
optimize performance, all threads are run in the kernel mode. The timer
accuracy is 1msec for sleep and wait related APIs. The different functionalities
of the kernel are broken down into small non-preemptive sections. So, during
system call preemption is turned off for only short periods of time. Also,
interrupt servicing is preemptable. That is, it supports nested interrupts. It
uses memory management unit (MMU) for virtual memory management.

VxWorks

VxWorks is a real-time operating system developed as proprietary software by


Wind River Systems. It is a high performance, Unix like, scalable RTOS, and
supports ARM, Pentium, Intel X-Scale, Super H and other popular processors
for embedded system design.

• VxWorks design is hierarchical and well suited for hard real time
applications. It supports kernel mode execution of tasks for fast
execution of application codes.
58
January 9,
EMBEDDED C BY NAGAVELLY SREEDHAR
2018

• VxWorks is supported with powerful development tools that make it easy


and efficient to use.
• Many simulation tools, time-performance analysis tools, debug and test
tools are provided, making VxWorks as an RTOS that supports
development of almost any embedded application, providing a complete
solution for all stages of the design cycle.

Some important features of VxWorks RTOS are,


1. Multitasking environment using standard POSIX scheduler

2. Ability to run two concurrent Operating systems on a single processing layer


3. Multiple file systems and systems that enable advanced multimedia
functionality
4. Synchronization using a full range of IPC options
5. Different context saving mechanisms for the tasks and ISRs

59

You might also like