Ep 2
Ep 2
3.3.7 Registers
27
The Embedded Environment
28
Interrupts
To use the global variable as a loop counter within a function, the following
code could be used:
int somefunc() {
ScratchPad.asShort=0;
while (ScratchPad.asShort < 10) {
// some code
ScratchPad.asShort += 1;
} // end while
return (someIntValue);
}
Example 3: Using globally allocated data space in a function
3.4 Interrupts
2The @ symbol uses the address allocated to __longIX for the new symbol myTemp.
This is not standard C so the syntax your compiler provides may be different.
29
The Embedded Environment
NOTE
It is vital that you understand how your target hardware implements interrupts as
this affects both the service routines you must write and how you write them.
In general, you must write an interrupt service routine for each interrupt your
target hardware can detect even if the handler consists solely of a return from
interrupt or a similar instruction.
Interrupts are asynchronous: they are events that can occur during, after, or
before an instruction cycle. Interrupt acknowledgement can be either
synchronous or asynchronous. Most interrupt acknowledgement is synchronous, the
instruction currently being executed is completed before the interrupt is
acknowledged.
Theoretically, when the processor acknowledges an interrupt asynchronously it
halts execution of the current instruction and immediately services the interrupt.
The only asynchronously acknowledged interrupt is RESET. Since RESET
erases the state of the machine, it is a moot point whether the CPU actually
halts execution of the current instruction or not.
When the processor acknowledges an interrupt synchronously, it finishes
executing the current instruction and, before it performs a fetch for the next
instruction, it services the interrupt.
30
Interrupts
There are two general ways in which microcontrollers service interrupts, each
with several variations.
31
The Embedded Environment
On most chips, the interrupt process saves the state of the machine including
the current program counter, stack pointer, and register contents. This is done
to ensure that after an interrupt is serviced execution will resume at the
appropriate point in main program with no loss of data.
32
Interrupts
Some chips save the machine state automatically while others will only save a
portion of the machine state. In the second case it is up to the programmer to
provide code which saves the current state. Usually each interrupt handler will
do this before attempting anything else. The location and accessibility of the
saved state information varies from machine to machine. In most cases, it is
saved on a stack located in data memory.
For example the Motorola MC68HC705C8 saves the machine state in the stack
as follows:
1 1 1 Condition Codes
Accumulator
Index Register
0 0 0 PC High
33
The Embedded Environment
MC68HC705C8 the interrupt mask bit of the Condition Code Register is set to
prevent interrupts.
Some machines provide a small number of non-maskable interrupts (NMI).
Interrupts that can be disabled are maskable, those which you cannot disable are
non-maskable. For example, RESET is non-maskable regardless of the code
currently executing the CPU must service a RESET interrupt. Some
microcontrollers also designate software interrupts or BREAK instructions that
you can use as a non-maskable interrupt.
What happens after the CPU services an interrupt? This varies depending upon
target hardware. In general, the CPU first checks for any outstanding interrupts.
One some machines the CPU first fetches an instruction and then checks for
interrupts after executing this instruction. This guarantees that no matter how
many interrupts cue up, the machine will always step through program code and
no more than one interrupt handler will execute between each main program
instruction.
On most machines the CPU will check for interrupts before performing the
next instruction fetch. As long as it detects a pending interrupt it will service the
interrupt before fetching the next instruction. This means it is possible to halt a
program by continuously sending interrupts. On the other hand, it guarantees
that an interrupt is serviced before any more main program code is executed.
When an interrupt occurs the signal sets a register bit. When the CPU checks
for pending interrupts it reads the register for set bits. Upon completing an
interrupt handler, the appropriate bit in the register is cleared.
How does the CPU decide which interrupt to service first? Each interrupt a
chip can detect has a precedence, the chip services those interrupts with a
higher precedence first.
Microcontrollers vary widely in the types of interrupts they can detect. Some
general types are widely available in one form or another. The only universal
interrupt is RESET and some simple chips support no other interrupts.
34
Specific Interrupts
3.5.1 RESET
The RESET interrupt prompts the chip to behave as if the power has been
cycled. It does not actually cycle the power to the chip. This means that the
contents of volatile memory, typically data memory, can remain intact. The
reset vector contains the address of the first instruction that will be executed
by the CPU.
You can write an initialization routine to be executed before any other program
code which first checks specific locations in data memory for particular values
and then loads values into those locations. This can be used to check if the
RESET was cold, power cycled, or warm, power not cycled. Some compilers
support a initialization function which is executed upon RESET before the
main program.
On most chips, RESET causes the CPU to halt execution immediately and
restart itself. On some chips, RESET may finish the current instruction. Each
microcontroller performs a series of actions when it detects a RESET. For
example, when a RESET occurs on the Motorola MC65HC705C8 the
following actions occur:
1) Data direction registers are cleared
2) Stack pointer is set to 0x00FF
3) CCR I bit is set
4) External interrupt latch is cleared
5) STOP latch is cleared
6) WAIT latch is cleared
A RESET can occur because of a manual reset, a COP time out, low voltage,
initial power on, or an attempt to execute an instruction from an illegal address.
Some chips that support interrupts provide an instruction in the instruction set
which the programmer can use to halt program execution. This instruction
name is different for different devices.
The COP8SAA7 has a Software trap which occurs when the INTR instruction
is placed in the instruction register. The software trap is used for unusual and
35
The Embedded Environment
3.5.3 IRQ
IRQ interrupts are physical pins or ports on the chip which generate an
interrupt when they are sent a signal. Some chips do not support IRQ type
interrupts and those that do implement them in many different ways. The
number of pins available for IRQs varies widely from chip to chip.
The developer usually has the ability to configure the IRQ interrupts to detect
signals in specific ways. For example, they can be made sensitive to a signal
edge, a signal hold, or a signal fall.
For example, the Microchip PIC17C42a has an INT external interrupt pin. The
developer can set the interrupt trigger to be either the rising edge or falling edge
by setting an appropriate register bit. The INT interrupt can be disabled by
clearing the appropriate control bit.
3.5.4 TIMER
code in your program to touch the watchdog at regular intervals before the time
period expires. If your program leaves the watchdog too long without attention,
the configured time period passes with no touch instruction, the watchdog
activates the RESET interrupt.
This type of timer interrupt provides your program with an independent safety
net. Since the watchdog timer depends only upon the clock signals to do its job,
if your program ever fails the watchdog will realize that the computer is not
operating properly and will activate a RESET.
3.6 Power
Most microcontrollers support 4.5 to 5.5 Volt operation. There are also many
low voltage parts which are designed to work at 3 volts or less.
3.6.1 Brownout
3.6.2 Halt/Idle
Input and output are lines or devices which carry information between the
microcontroller and the outside world.
3.7.1 Ports
37
The Embedded Environment
input/output. Often the port state is set with a direction register which
determines if the port is input, output or input/output. When a port pin is an
output it is a latched output. This means that when the pin is in a given state,
set or unset, it will remain in that state until reset.
Microcontrollers usually contain several ports.
For example, the Microchip PIC16C74 has five ports called PORTA, PORTB,
PORTC, PORTD and PORTE. PORTA is 6 bit latch which is configured as
input or output using the register TRISA. PORTA can also be configured as
analog or digital using the ADCON1 register. PORTB is an 8 bit bi-directional
port with data direction register TRISB.
The National Semiconductor COPSAA7 contains four bi-directional ports:
PORTC, PORTG, PORTL and PORTF. Each bit can be configured as input,
output or trisate.
CAN
Controlled Area Network was developed by Bosh and Intel. It is a multiplexed
wiring scheme.
J1850
J1850 is the SAE (Society of Automotive Engineers) standard.
38
Input and Output
UART
A Universal Asynchronous Receiver Transmitter is a serial port adapter that
receives and transmits serial data with each data character preceded by a start bit
39
The Embedded Environment
and followed by a stop bit. There is sometimes a parity bit included. A UART is
used mainly as a serial to parallel and parallel to serial converter.
USART
A Universal Synchronous/Asynchronous Receiver Transmitter is a serial port
adapter used for synchronous or asynchronous serial communication.
Flash converter
Examines each level and decides what level the voltage is at. It is very fast, but
draws a great deal of current and is not feasible beyond 10 bits.
40
Miscellaneous
3.9 Miscellaneous
The clock monitor watches the clock and determines if it is running too slow. It
can activate a microcontroller reset.
3.10 Devices
ROM whose contents are set by masking during the manufacturing process.
3.10.3 OTP
41
4. Programming Fundamentals
It is necessary to understand some basic computer programming concepts
before learning C programming.
The most important thing to remember about computers is that they can do
only what they are instructed to do. To accomplish a meaningful task on a
computer, someone must give it exhaustive and very explicit instructions. A
collection of such instructions is a called a program and the person who writes
and revises these instructions is known as a programmer or developer.
There are several different number systems. We are used to the decimal number
system which is of base 10. This means that it has ten digits and coefficients are
multiplied by powers of 10. For example 456 is the same as 4(102) + 5(101) +
6(100) = 400 + 50 + 6 = 456.
Computers use the binary number system with base 2: it has two digits (0 and 1)
and the coefficients are multiplied by powers of 2. For example 110 is the same
2 1 0
as 1(2 ) + 1(2 ) + 0(2 ) = 6.
The hexadecimal number system is often used as it is easier to read than binary
numbers. It is base 16 and uses 0-9 and A-F to represent values.
43
Programming Fundamentals
When people read and write they use extremely powerful and flexible coding
systems called alphabets. Computers, however, can only handle information
written in the most simple coding system possible binary notation. The
binary alphabet has only two components: 1 and 0.
44
Binary Information
Signal
Figure 9: Data storage VS. data value
In the example the data is stored at the address 00 Hex. The data stored at that
location has the value 1101 1101 binary or DD hexadecimal.
One bit in computer memory can record either 0 or 1 because it contains a
single binary digit which can exist in only two states. Two bits read in sequence,
however, can record four possible numbers: 0, 1, 2, and 3 because two bits can
exist in the states 00, 01, 10 and 11. As with decimal notation, the first digit
records the multiples of one included in the number. The second digit records
the multiples of two since the computer only has two digits available. Adding a
third digit allows for the encoding of multiples of 4.
Bits are often grouped in sets. 8 bits make 1 byte, while 16 bits make one word.
Standard terminology refers to 210 (1024) bytes as a kilobyte.
A programmer can give the computer information and instructions using long
strings of 1s and 0s. However, this process would be very time consuming and
prone to error. To resolve these problems programmers have developed
languages in which to write programs. Languages help the programmer by
making the job of programming a computer faster, more efficient, and more
reliable.
45
Programming Fundamentals
Computer memory is divided up into addresses. Each address holds an 8 bit (or
1 byte) value. The number of address lines determines the number of locations
available. For example, the MC68HC05C8 can address 8192 bytes of memory.
Sine each bit can hold one of two values (0 or 1) and 213 = 8192 we know that
there are 13 address lines. The first address will be the value 0 0000 0000 0000
(0x0000) and the last address will be the value 1 1111 1111 1111 (0x1FFF).
Microcontrollers have different addressing modes which allow them to access
locations in memory as quickly as possible. For example, the first 256 locations
on the Motorola MC68HC705C8 can be accessed using direct addressing mode
where the CPU assumes that the high byte of the address is 00000000.
46
Instruction Sets
8 bit microcontrollers usually use byte size instruction codes. Each instruction
has two possible components: an opcode and an operand. The opcode is the
function that the instruction performs while the operand is data used by the
opcode. Neither opcodes nor operands are restricted to 1 byte.
There are several different types of addressing modes. An addressing mode is
simply the means by which an address is determined. Some common modes are
immediate data, direct address, and indirect or indexed address.
4.6.1 Assembler
47
Programming Fundamentals
The processor can execute these instructions at a very high speed. RISC uses a
technique called pipelining the processing of instructions can be overlapped.
For example, one instruction can be read from memory while another is
decoded and another is executed. Many RISC machines have a single
instruction size and a small number of addressing modes.
Some microcontrollers are called SISC (Specific Instruction Set Computer)
machines. This is based on the fact that the instruction sets are designed
specifically for control purposes.
Instructions Instruction Cycle Address Modes
Size
MC68HC705C8 58 8, 16 or 24 bit 2 to 11 10
COP8SAA7 56 8, 16 or 24 bit 1 to 5 10
PIC16C54 33 12 bit 1 or 2 3
PIC16C74 35 14 bit 1 or 2 3
Table 8: Instruction set comparisons
48
The Development of Programming Languages
One of the most important tools that programmers developed to deal with new
high level languages is the language compiler.
49
Programming Fundamentals
4.9 Compilers
When programs were written in the past often the development computer was
not powerful enough to hold the entire program being developed in memory at
one time. Historically, programs had to be divided into separate modules where
each module would be compiled into object code and a linker would link the
50
Cross Development
object modules together. Our development machines today are very powerful
and the use of a linker is no longer absolutely necessary.
Many implementations of C provide function libraries which have been pre-
compiled for a particular computer. These functions serve common program
needs such as serial port support, input/output, and description of the
destination computer. Functions within libraries are usually either linked with
modules which use them or included directly by the compiler if the compiler
supports library function inclusion.
When your program has been pre-processed, compiled and linked, the
destination computer will be able to read and execute your program.
A cross compiler runs on one type of computer and produces machine code for
a different type of computer. While many 8 bit embedded microcontrollers can
support sophisticated and extremely useful programs, they are not powerful
enough to support the resource needs of a C development environment. How
does a developer create and compile programs for an 8 bit microcontroller? By
using a cross compiler.
51
Programming Fundamentals
programmer to imprint the translated program into the memory of the 8 bit
microcontroller.
Simulator
A simulator is a software program which allows a developer to run a program
designed for one type of machine (the target machine) on another (the
development machine). The simulator simulates the running conditions of the
target machine on the development machine.
Using a simulator you can step through your code while the program is running.
You can change parts of your code in order to experiment with different
solutions to a programming problem. Simulators do not support real interrupts
or devices.
An in-circuit simulator includes a hardware device which can be connected to
your development system to behave like the target microcontroller. The in-
circuit simulator has all the functionality of the software simulator while also
supporting the emulation of the microcontrollers I/O capabilities.
Emulator
An emulator or in-circuit emulator is a hardware device which behaves like a
target machine. It is often called a real time tool because it can react to events as
the target microcontroller would. Emulators are often packaged with monitor
programs which allow developers to examine registers and memory locations
and set breakpoints.
52
Cross Development
Problem Specification
The problem specification is a statement of the problem that your program will
solve without considering any possible solutions. The main consideration is
explaining in detail what the program will do.
Once the specification of the problem is complete you must examine the
system as a whole. At this point you will consider specific needs such as those
of interrupt driven or timing-critical systems.
For example, if the problem is to design a home thermostat the problem
specification should examine the functions needed for the thermostat. These
function may include reading the temperature, displaying the temperature,
turning on the heater, turning on the air conditioner, reading the time, and
displaying the time. Based on these functions it is apparent that the thermostat
will require hardware to sense temperature, a keypad, and a display.
Tool/Chip Selection
The type of application will often determine the device chosen. Needs based on
memory size, speed and special feature availability will determine which device
will be most appropriate. Issues such as cost and availability should also be
investigated.
The tools available will also impact a decision to develop with a certain device.
It is essential to determine if the development decisions you have made are
possible with the device you are considering. For example, if you wish to use C
you must select a device for which there is a C language compiler. It is also
useful to investigate the availability of emulators, simulators and debuggers.
Software Plan
The first step in the software plan is to select an algorithm which solves the
problem specified in your problem specification. Various algorithms should be
considered and compared in terms of code side, speed, difficulty, and ease of
maintenance.
Once a basic algorithm is chosen the overall problem should be broken down
into smaller problems. The home thermostat project quite naturally breaks
down into modules for each device and then each function of that device.
For example, the thermostat may have a display to the LCD display module and
a read from the keyboard module.
53
Programming Fundamentals
Device Plan
The routines for hardware specific features should also be planned. These
routines include:
1) Set up the reset vector
2) Set up the interrupt vectors
3) Watch the stack (hardware or software)
4) Interact with peripherals such as timers, serial ports, and A/D
converters.
5) Work with I/O ports
Code/Debug
The modules from the software plan stage are coded in the project language.
The coded modules are compiled or assembled and all syntactic error are
repaired.
Debugging should consider issues such as:
Syntactic correctness of the code
Timing of the program
Test
Each module should be tested to ensure that it is functioning properly. This
testing is done using simulators and/or emulators. It is also important to test
the hardware you will be using. This is easily done by writing small programs
which test the devices.
Integrate
The modules must be combined to create a functioning program. At this point
is important to test routines which are designed to respond to specific
conditions. These routines include interrupt service and watchdog support
routines. The entire program should now be thoroughly tested.
54
5. First Look at a C Program
Traditionally, the first program a developer writes in the C language is one
which displays the message Hello World! on the computer screen. This is
a sensible beginning for traditional C platforms where conventional input and
output are important and fundamental concepts.
In the world of 8 bit microcontrollers device input and output play radically
different roles. Programs rarely have access to keyboard input or screen output
devices which are common in traditional C programming 3.
The following introductory program is representative of microcontroller
programming. The program tests to see if a button attached to a controller port
has been pushed. If the button has been pushed, the program turns on an LED
attached to the port, waits, and then turns it back off.
#include <hc705c8.h>
// #pragma portrw PortA @ 0x0A; is declared in header
// #pragma portw PortADir @ 0x8A; is declared in header
#define INPUT 1
#define OUTPUT 0
#define ON 1
#define OFF 0
#define PUSHED 1
void main(void){
PortADir.0 = OUTPUT; //set pin 0 for output (light)
PortADir.1 = INPUT; //set pin 1 for input (button)
while (1){ // loop forever
if (PortA.1 == PUSHED){
wait(1); // is it a valid push?
if (PortA.1 == PUSHED){
PortA.0 = ON; // turn on light
wait(10); // delay (light on)
PortA.0 = OFF; // turn off light
}
}
}
} //end main
Example 6: A typical microcontroller program
3 Most C compilers for 8 bit microcontrollers do not use stdio libraries as these
libraries provide functions for input and output rarely used on 8 bit machines.
55
First Look at a C Program
NOTE
Always comment your code. Even if you are sure no other programmer will ever
look at your code, a near impossibility, you will still need to understand it. You will
often rework code months and even years after it was originally written. Comments
drastically improve code readability.
56
Preprocessor directives
#include <hc705c8.h>
#include is one of the most commonly used preprocessor directives. When
the preprocessor reaches this directive it looks for the file named in the
brackets. In the example above the preprocessor searches for the file
hc705c8.h which contains device specific specifications for the Motorola
68HC705C8.
If the file is found the preprocessor will replace the #include directive with
the entire contents of the file. If the file is not found the preprocessor will halt
and give an error.
In the example the #include directive is used to include the contents of a
header file. By convention, C language header files have the .h extension.
Header files contain information which is used by several different sections, or
modules, of a C program as they contain preprocessor directives and
predefined values which help to describe the resources and capabilities of a
specific target microcontroller.
#define ON 1
#define OFF 0
#define is another commonly used preprocessor directive which is used to
define symbolic constants. Programs often use a constant number or value
many times. Instead of typing in the actual number or value throughout the
program, you can define a symbol which represents the value. When the
preprocessor reaches a #define directive, it will replace all the occurrences of
the symbol name in your program with the associated constant. Constants are
useful for two specific reasons:
1) Increasing program readability. A symbolic name is more descriptive
than a number. For instance, the name ON is easier to understand than the
value 1. Using symbolic constants enhances the readability of your
programs and makes them easier to test, debug and modify.
2) Increasing program modifiability. Since the symbolic constant value is
defined in a single place, only one change is necessary if you wish to modify
the value: in the #define statement. Without the #define statement it
would be necessary to search through the entire program for every place
the value is used and change each one individually.
In the statements #define ON 1 and #define OFF 0, the symbols ON
and OFF are assigned the values 1 and 0 respectively. Everywhere the
57
First Look at a C Program
preprocessor sees the symbol ON it will replace it with the constant 1; where it
sees OFF it will replace it with the constant 0.
5.3 C Functions
C programs are built from functions. Functions typically accept data as input,
manipulate data and return the resulting value. For example, you could write a
C function that would take two numbers, add them together and return the sum
as the result.
When a computer runs a C program, how does it know where the program
starts? All C programs must have one function called main()which is always
the first function executed.
Notice the notation for main(). You specify a function name by following
the name with parentheses. This is the notation used by the C compiler to
determine when it has encountered a function. As long as the name is not a
recognised C command, called a reserved word, the compiler will assume it is a
function if it is immediately followed by a pair of parentheses. The parentheses
may also surround a list of input arguments for the function.
58
C Functions
void main(void){
//function statements
}
Example 7: Syntax for the main( ) function
Example 7 is the definition for the main() function in Example 6. All the
statements that fall between the two braces, {}, have been omitted for
example purposes.
The first use of the term void, prior to the word main, indicates to the
compiler that this main() function does not return any value at all. The
second use of the term void, between the parentheses, indicates to the
compiler that this main() function is not sent any data in the form of
arguments.
Braces must surround all statements which form the body of a function. Even
functions with no statements in their body require the braces the braces after
a function header indicate to the compiler that you are providing the definition
of a function.
The main() function can execute code from other functions. This is referred
to as calling another function. The calling function must know about the called
function in order to execute its code. A function knows about another function
in two ways:
1) The entire definition of the called function is positioned earlier in the
source file than the calling function.
2) A function prototype of the called function is included before the calling
function in the same source file.
A function prototype describes details of the requirements of a function so
that any program code that calls that function will know what information the
called function requires. The following is a typical function prototype:
void wait(registera);
The example above is a function prototype for a function called wait(). This
function is preceded by the return value void; therefore, it does not return a
value. Unlike main(), the wait() function does expect to receive an
argument, called a parameter. The type of the parameter (registera) is
59
First Look at a C Program
important. It indicates the type of value the parameter will hold a value of
type registera.
60
The Function Body
pour into the coffee maker depends upon the number of cups you want to
make. At some point in your instructions, you need to allow the person
following them to make a decision about the number of cups needed and,
therefore, the amount of water needed. You might say: if you want to make 4
cups of coffee, then use 5 cups of water.
In C decisions are made using control statements. Control statements can select
between two or more paths of execution and repeat a set of statements a given
number of times. Some common control statements are:
while
while(1){
// statements
}
The while() control statement instructs the computer to repeat a set of
instructions (loop) as long as a condition is valid. The condition is an
expression placed in the brackets which follow the while statement. C
considers any condition which does not evaluate to 0 to be true and any
condition which does evaluate to 0 to be false.
In Example 6 the condition is the integer 1b (binary), which is interpreted as
true. Therefore, once the computer begins to execute statements inside the
braces of the while loop, it will not terminate until the computer
malfunctions or is turned off. This kind of loop is often called an infinite loop.
In traditional C programming, an infinite loop is usually avoided. However, it is
often used in embedded systems programming. An embedded controller
typically executes a single program infinitely. Only when the controller is
reset or turned off will the loop terminate.
if
if (PortA.1 == PUSHED){
PortA.0 = ON;
}
Example 9: The if statement syntax
while (1){
if (PortA.1 == PUSHED){
PortA.0 = ON;
}
}
Example 10: Nesting if and while statements
When the if decision is placed inside a while loop, the program will test bit
1 in PortA regularly. Assume a button is attached to pin 1 of port A and an
LED to pin 0 of PortA. We have written a small control program which will
continually test the button attached to pin 1. When the button is pushed, bit 1
of PortA will be set. When bit 1 is set and the if statement is executed, bit 0 is
set. The LED attached to PortA pin 0 will be set to 1 and will light up.
A program can delegate a task by calling another function. Once the program
turns on the LED in Example 10 it never turns it off. Remember, the while
loop is an infinite loop. How can we solve this problem?
One solution is to write a function called wait()which creates a delay and
then turn the LED off. Consider the following example code fragment:
while (1)
{
if (PortA.1 = PUSHED)
{
PortA.0 = ON;
wait(10); \\ wait ten seconds
PortA.0 = OFF;
}
}
Example 11: Calling one function from another
When the wait() function is used and the button is pushed, the program
turns the LED on by setting bit 0 of PortA. The wait() function causes a
delay of ten seconds. After the wait function has finished and ten seconds have
passed, the program turns off the LED by clearing bit 0 of PortA.
62
The Embedded Difference
Most embedded systems programs include a header file which describes the
target processor. These header files contain descriptions of reset vectors, ROM
and RAM size and location, register names and locations, port names and
locations, register bit definitions and macro definitions. Most compiler
companies will provide header files for devices supported by their compilers.
Another important aspect of device knowledge is the limits of the device for
which the program is written. For example, a certain device may have very
limited memory resources and great care must be taken in developing programs
which use memory frugally. Along with issues of size comes issues of speed.
Different devices run at different speeds and use different techniques to
synchronise with peripherals. It is essential that you understand device timing
for any embedded systems application.
The previous section mentioned the regular use of the infinite loop
while(1). Embedded developers often use program control statements
which are avoided by other programmers. For example, the goto statement is
used regularly by embedded developers and is often condemned by other
programmers.
Many developers prefer to write some code segments in assembly language for
reasons of code efficiency or while converting a program from assembly
language to C. Most compilers for 8 bit microcontrollers allow the inclusion of
inline assembly, assembly language in a C program.
63
First Look at a C Program
The following two definitions of the wait() function show the function
written in C and the equivalent function in Motorola 68HC705C8 assembly
language.
//C function
void wait(registera delay){
while (--delay);
}
64
6. C Program Structure
The previous section described some typical features of a very simple program.
In this section we will examine in greater detail the building blocks of the C
language.
A C program is built from three components:
1) Directives are directives handled directly by the preprocessor
2) Declarations are instructions for the compiler to record the type
and associated memory locations of symbols
3) Statements are the executable instructions in a program
Declarations define the name and type of identifiers used in your program. One
benefit of programming in a high level language is the ability to construct
generic groups of instructions, called functions, to perform tasks whose steps
are not dependant upon specific values. For example, you can write
instructions to add together two numbers without knowing the values of the
numbers. How can this be done? Through the use of identifiers.
An identifier can either represent a value, called a variable, or a group of
instructions, called a function. C identifiers represent addresses in computer
65
C Program Structure
The compiler allocates memory for all identifiers. As the compiler reads a
program, it records all identifier names in a symbol table. The compiler uses the
symbol table internally as a reference to keep track of the identifiers: their name,
type and the location in memory which they represent.
When the compiler finishes translating a program into machine language, it will
have replaced all the identifier names used in the program with instructions that
refer to the memory addresses associated with these identifiers.
66
Identifier Declaration
Notice that even though the two identifiers are different words, the first five
significant characters are identical.
Identifiers which represent constant data values are allocated from computer
program memory space. Identifiers which represent constant data values do not
require alterable memory: once the value of a constant has been written in
67
C Program Structure
memory it need never change. Therefore, the compiler will allocate a block of
its program memory space, usually in ROM, for each of these identifiers4.
To declare a constant data value, use a declaration such as:
const int maximumTemperature = 30;
Function identifiers are not altered during program execution. Once the value
of a function has been written in the computers memory it need never change.
When a function is defined, the compiler places the program instructions
associated with the function into ROM. What happens to the local variables
used in a functions body of statements? The compiler will write in the data
memory addresses where local variable values will be stored in RAM when the
program runs.
6.3 Statements
The compiler will generate an instruction to store the value 20 in the RAM
memory location set aside for the currentTemperature variable.
4 This is not always the case. However, you can safely assume that all C constant
68
Statements
Forgetting the semicolon at the end of the first line forces the compiler to read
both lines as one statement instead of two. According to the compiler you have
written the following instruction:
currentTemperature = 20 currentTemperature = 25;
Notice that the C compiler does not care about white space between tokens as
it reads through your program. White space includes space, tab and end-of-line
characters. On some computers the end of line will be a single linefeed
character, while on others it will be a linefeed and carriage return together. C
compilers ignore both carriage returns and linefeeds.
When you write a C function you must include function statements as part of
the function definition. Statements belonging to a function are indicated by
surrounding them with braces which immediately follow the function header.
For example, Example 6 in the previous section has braces surrounding all the
statements in the main() function.
You may create statement blocks at other times in your program. For example,
notice the braces after the while and if statements:
while (1){ // this brace begins the block for while
if (PortA.1 = 1){ // this brace begins the if block
PortA.0 = 1;
wait(10);
PortA.0 = 0;
} // this brace closes the block for if
69
C Program Structure
70
7. Basic Data Types
It is easy to see how the computer stores binary values in memory as that is the
manner in which its memory is structured. We have also seen how the
computer stores other types of numbers, such as hexadecimal and decimal, by
converting them to binary form. This section examines how other types of data
can be used.
A computer can store a number in its memory. What about a character? People
use alphabets to encode linguistic information while computers must use binary
notation. To resolve this problem, computer programmers have settled on
encoding schemes for representing characters with numbers such as ASCII
(American Standard Code for Information Interchange) encoding. In the ASCII
character set each character is associated with an integer value. When the
computer needs to store a character it uses the ASCII integer value associated
with that character and stores the number in binary notation.
Data types act as filters between your program and computer memory. Data
types in C provide rules for the storage and retrieval of information from
computer memory. C data types also provide a set of rules for acceptable data
manipulation.
The primary distinguishing characteristic of a data type is its size. The size of a
data type indicates the amount of memory the computer must reserve for a
value of that type. For example, on 8 bit microcontrollers the int data type
(used for storing integer values) is a single byte in size6 while a long or long
int data type is two bytes in size. When the compiler translates a program it
must write the instructions to account for this size difference. The computer
6 The size of the int data type in C is the same as the amount of information the
computer can process. Since 8 bit microcontrollers work with a byte at a time the size
for int is 1 byte (or 8 bits). On most modern computers int varies from 24 bits to 64
bits.
71
Basic Data Types
will know to set aside a single byte for all the int values in your program and
two bytes for all the long values.
A declaration can also be used to ensure that a variable will be assigned a certain
value when it is allocated. When this is done the compiler allocates the
appropriate space for the variable and immediately assigns a value to that
memory location, for example: int currentTemperature = 20;
allocates 1 byte for the variable currentTemperature and assigns it the
value 20. This is called initializing the variable.
Initialization ensures that a variable contains a known value when the computer
executes the first statement which uses that variable. If variables are not
initialized in their declarations, their values are unknown until they are
initialized.
When the compiler comes across a variable declaration it checks that the
variable has not previously been declared and then allocates an appropriately
72
Basic Data Types
sized block of RAM. For example, an int variable will require a single word (8
bits) of RAM or data memory7.
When the compiler allocates memory for a variable it decides where to place the
variable value based on the existing entries in its symbol table. Since the
compiler cannot know what value lies at the address allocated for a particular
variable at compile-time, you can not depend upon a specific value for a
variable the first time it is used.
Compile-time is the point at which the compiler translates a program into
machine code. Run-time indicates the point at which the machine code is
executed on the host computer. It is useful to remember that compilers have
little or no knowledge about a machines internal state at run-time.
Declarations that initialise variables are very useful they ensure that you can
predict what a variable memory location will contain at run-time. When the
compiler reads a declaration which also initializes a variable it first allocates an
appropriate block of memory, then immediately loads the appropriate value into
that location.
Please note that variable declarations which contain an initialization will
automatically generate machine code to place a value at the address allocated for
the variable. Normal variable declarations do not generate any code because the
machine code contains the address allocated for such a variable. This is not the
case for either global variables or static local variables if they are not initialized
in their declaration the compiler will initialize them by setting their initial values
to 0. The compiler will produce machine instructions to load the 0 value into
the appropriate addresses.
7The amount of memory required for an integer variable varies from computer to
computer. 8 bit microcontrollers have a natural integer size of 8 bits.
73
Basic Data Types
If you declare a variable outside all statement blocks, the scope of the variable
reaches from its declaration point to the end of the source file. Variables
declared in this manner are called global variables because they can be used by
any program code which comes after them in the same source file.
A variable declared outside a statement block can be accessed by any statement
in your program by declaring the variable in a certain way. In order for a
statement block or separate program file to access to such a variable, it must be
declared as an external symbol. This means using the extern storage class
modifier. For example, the following declaration tells the compiler to look for
the original declaration of currentTemp in another file or below in the same
file: extern int currentTemp;.
The use of extern in a variable declaration is similar to the use of a function
prototype it informs the compiler of a variables name and data type so that it
can be used before it is actually defined. As with function prototype
declarations, the compiler does not allocate memory when it sees an extern
variable declaration.
A variable declared inside a statement block has a scope from the declaration to
the end of the statement block. Variables declared inside a statement block are
called local variables, as they are accessible only to statements which follow
them within the same statement block. Typically, programmers will declare
variables whose scope is local to a specific function. The variable name and
value will be defined only within that function and other functions cannot
directly refer to the variable.
What happens if you have two functions which each contain local variables with
the same name? Since a variable is local to its respective functions the compiler
can distinguish between identically named variables. A variable name must be
unique within its scope.
What happens when scopes overlap? The most recently declared instance of a
variable is used. If you declare a global variable called temp outside all
statement blocks and a local variable called temp inside your main()
74
Basic Data Types
function, the compiler gives the local variable precedence inside main().
While the computer executes statements inside main()s scope (or statement
block), temp will have the value and scope assigned to it as a local variable.
When execution passes outside main()s scope, temp will have the value and
scope assigned to it as a global variable.
A function data type allocates memory for the type of value the function
returns. Function identifiers work differently than variables. When a function is
defined a data type for the function must be included. Instead of indicating the
amount of memory set aside for the function itself it indicates the amount of
memory the compiler needs to reserve for the value returned by the function.
For example, a function of type int returns a signed integer value and 8 bits
are reserved for the return value.
Suppose we have a function defined as follows:
void wait(int timeInSeconds);
The void keyword indicates to the compiler that the function will not return a
value; therefore, no memory is allocated for a return value.
75
Basic Data Types
Parameter data types indicate the size of memory reserved for function
parameter values. We define a data type for the parameter the function expects
to be passed when it is called. The declaration of timeInSeconds as an
int in the function declaration void wait(int timeInSeconds);
tells the compiler to allocate a single byte to hold the parameter value when the
function is called.
The void keyword can also be used in a function parameter list:
void main(void);
This indicates to the compiler that the function does not expect to receive any
parameter values when called. The compiler does not allocate any memory for
void parameters.
The C language character data type, char, stores character values and is
allocated 1 byte of memory space. Microcontrollers do not often manipulate
alphabetic information, but sometimes it is required. The most common use of
alphabetic information is reading input from a keyboard device, where each key
typed is indicated by a character value. The char type uses a single byte of
memory and stores the value of each character by storing its ASCII code.
When assigning a character value to an identifier you must place the character in
single quotes. The quotes tell the compiler that the value is a character constant
and not the name of another identifier.
char firstLetter;
firstLetter = 'a';
firstLetter = a;
Example 17: Assigning a character value
The first assignment in the example above places the ASCII value for the
character a in the memory location assigned to the firstLetter variable.
When the compiler reads the second assignment statement, it assumes that a is
the name of a second variable. If no variable called a exists the compiler will
generate an error.
76