Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
20 views50 pages

Ep 2

The document discusses various memory types used in microcontrollers, including flash memory, registers, and scratch pads, emphasizing their roles in application development and resource management. It also covers the importance of interrupts for efficient microcontroller operation, detailing how they are handled, acknowledged, and serviced. Additionally, it explains the significance of specific interrupts like RESET and the implications for program execution and memory state preservation.
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)
20 views50 pages

Ep 2

The document discusses various memory types used in microcontrollers, including flash memory, registers, and scratch pads, emphasizing their roles in application development and resource management. It also covers the importance of interrupts for efficient microcontroller operation, detailing how they are handled, acknowledged, and serviced. Additionally, it explains the significance of specific interrupts like RESET and the implications for program execution and memory state preservation.
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/ 50

Memory Addressing and Types

produce microcontroller versions for use on an evaluation board where chip


access to its own onboard ROM is turned off and replaced with external
EPROM or EEPROM storage. This allows developers to test application code
in cycles by downloading it to the board, programming the code into the
EPROM or EEPROM, and debugging it as it executes in the target hardware.

3.3.6 Flash Memory

Flash memory is an economical compromise between EEPROM and EPROM


technology. As with EEPROM high voltage is applied to erase and rewrite flash
memory. However, unlike EEPROM, you can not selectively erase portions of
flash memory – you must erase the entire block as with EPROM devices. Many
manufacturers are turning to flash memory. It has the advantages of not
requiring special hardware and being inexpensive enough to use in quantity.
Manufacturers often provide customers with microcontroller products whose
ROM is loaded with a boot or configuration kernel where the application code
is written into flash memory. When the manufacturer wants to provide the
customer with added functionality or a maintenance update, the hardware can
be reprogrammed on site without installing new physical parts. The hardware is
placed into configuration mode which hands control to the kernel written in
ROM. This kernel then handles the software steps needed to erase and re-write
the contents of the flash memory.
Another useful implementation of flash memory includes a device which can
connect electronically to a computer owned by the manufacturer. The
configuration kernel connects to the manufacturer’s computer, downloads the
latest version of the control application and writes this application to flash
memory. Such elaborate applications are typically beyond the resources of an 8
bit microcontroller; we mention the example to show the advantage of
programmable ROM technologies.

3.3.7 Registers

The CPU maintains a set of registers which it uses to store information.


Registers are used to control program execution and maintain intermediate
values needed to perform required calculations. Some microcontrollers provide
access to CPU registers for temporary storage purposes. This can be extremely
dangerous as the CPU can at any time overwrite a register being used for its
designated purpose.

27
The Embedded Environment

8 bit microcontrollers do not often provide resources for register memory


outside the CPU. This means that the C register keyword is meaningless
because the compiler can not dedicate a CPU register for data storage.
Some C implementations will set aside RAM for special purpose pseudo-registers
to use when your application attempts certain operations. For example, if you
attempt a 16 bit math operation, the compiler can dedicate a portion of base-
page RAM for 16 bit pseudo-registers which store values during math
operations. You can use these special registers for temporary purposes in places
where your code will not require them for their intended purpose. You must be
careful, if the compiler uses a pseudo-register it will overwrite current contents.

3.3.8 Scratch Pad


Microcontrollers are typically very short on resources, especially data memory
space. Many C compilers use some available RAM for internal purposes such as
pseudo-registers. An efficient C compiler will support scratch pads in data
memory. A scratch pad is a block of memory which can be used for more than
one purpose.
The simplest way to conserve data memory is through the judicious use of
global variables. For example, in a traditional C environment developers create
local counter variables every time they are required because data memory is
 See 11.6 cheap and plentiful. However, embedded systems developers will often create
Unions global counter variables. Any function can then use this allocated block of data
memory when a counter or temporary variable is needed. Examine the
following union called ScratchPad which is declared globally:
union {
int asInt;
char asChar;
short asShort;
long asLong;
void near * asNPtr;
void far * asFPtr;
struct {
short loByte;
short hiByte;
} asWord;
} ScratchPad;
Example 2: Using a union structure to create a scratch pad

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

Some C compilers support a C extension which fixes the location of a symbol


in memory. In these cases, the compiler typically does not check that the
memory specified is not being used by other data. You can use this feature to
manage how variables are placed in data memory space. More importantly you
can overlay one variable symbol on top of the memory allocated for another.
This is a useful technique for reusing allocated variable space.
For example, it is possible to reuse internal the pseudo-register variables created
by the compiler in portions of your code that do not use them for their
designated purpose. For example, if your compiler creates the 16 bit pseudo
index register __longIX you can reuse this 16 bit location with the following
statement2:
long int myTemp @ _ _ longIX;
You must ensure that you understand exactly how and when the compiler uses
these internal variables before you reuse the variable space.
Fixing a symbol at a specific memory location will likely affect the optimization
a compiler will perform with the symbol. It may be more worthwhile to avoid
this method of overlaying memory in favour of the savings generated by the
compiler’s optimizer.

3.4 Interrupts

Interrupts allow the microcontroller to interact with its environment. If your


microcontroller does not have interrupts you must poll peripherals to

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

determine if they require servicing. It is much more efficient to have peripheral


devices inform, or interrupt, the controller when they require servicing.
An interrupt is a signal sent to the microcontroller which causes it to stop its
current execution and perform another action. The chip stops executing your
main program and executes some other code. Interrupts can be edge triggered
(rising or falling) or level triggered.

3.4.1 Interrupt Handling

Code executed by an interrupt is not generally considered part of the main


application. Since this code handles the cases where an interrupt occurs, it is
called an interrupt handler or an interrupt service routine.

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.

3.4.2 Synchronous and Asynchronous Interrupt Acknowledgement

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

3.4.3 Servicing Interrupts

There are two general ways in which microcontrollers service interrupts, each
with several variations.

 Vectored Arbitration System


Some machines reserve a portion of program memory for interrupt vectors.
The location of each particular vector in program memory may vary from
processor to processor but it cannot be changed by the programmer. The
programmer can only change the data at each vector location.
Each interrupt vector contains the address of that interrupt’s service routine.
When the compiler allocates program memory for interrupt handlers, it places
the appropriate address for the handler in the appropriate interrupt vector. To
help the compiler you must usually tell it where the interrupt vector for each
interrupt is located in program memory.
When an interrupt occurs, global interrupts are first disabled to prevent an
interrupt service from being itself interrupted. On the COP8SAA7 this
involves setting the GIE bit to zero. The machine then reads the address
contained at the appropriate interrupt vector. It then jumps to the address and
begins executing the interrupt service code. Vectored interrupts are much faster
than non-vectored.
The following are sample interrupts from the National Semiconductor
COP8SAA7:

31
The Embedded Environment

Rank Source Description Vector Address *


1 Software INTR Instruction 0bFE - 0bFF
2 Reserved Future 0bFC - 0bFD
3 External G0 0bFA - 0bFB
4 Timer T0 Underflow 0bF8 - 0bF9
5 Timer T1 T1A/Underflow 0bF6 - 0bF7
6 Timer T1 T1B 0bF4 - 0bF5
7 MICROWIRE/PLUS BUSY Low 0bF2 - 0bF3
8 Reserved Future 0bF0 - 0bF1
9 Reserved Future 0bEE - 0bEF
10 Reserved Future 0bEC - 0bED
11 Reserved Future 0bEA - 0bEB
12 Reserved Future 0bE8 - 0bE9
13 Reserved Future 0bE6 - 0bE7
14 Reserved Future 0bE4 - 0bE5
15 Port L/Wakeup Port L Edge 0bE2 - 0bE3
16 Default VIS Instruction Execution 0bE0 - 0bE1
without any interrupts
* b represents the Vector to Interrupt Service routine (VIS) block. VIS and the vector
table must be within the same 256 byte block. If VIS is the last address of a block
the table must be in the next block.
Table 5: Sample vectored interrupts

 Non-Vectored Priority System


When an interrupt occurs, the PC branches to a specific address. At this
address the interrupts must be checked sequentially to determine which one has
caused the interrupt.
This scheme can be very slow and there can be a large delay between the time
the interrupt occurs and the time it is serviced. However, the programmer can
set the interrupt priority and non-vectored interrupts are feasible for
microcontrollers with less than five interrupts.

3.4.4 Interrupt Detection

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

Program Counter Low

Figure 8: Saving the machine state on the MC68HC705C8

Many C compilers for embedded microcontrollers reserve a portion of data


memory for internal uses such as for pseudo-registers. You must check your
compiler documentation to determine what code you must write to preserve the
information located in these memory blocks. Some compilers document their
internal data memory overhead so you can determine what you must preserve
in your interrupt handlers while others automatically generate code to preserve
this data.
One way to conserve memory is to avoid unnecessarily preserving data. If your
compiler creates a pseudo register for 16 bit math operations and your interrupt
handler does not use this pseudo register, then you need not preserve its state.

3.4.5 Executing Interrupt Handlers

To minimize the possibility of an interrupt handler being itself interrupted, the


microcontroller will usually disable interrupts while executing an interrupt
handler. The method of doing this varies from chip to chip. Some platforms
automatically disable interrupts, while others leave this to the programmer.
Masking interrupts is useful during timing critical sections of code. The
COP8SAA7, for example, has a GIE (Global Interrupt Enable) which is set to
allow interrupts or cleared to prevent interrupts. On the Motorola

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.

3.4.6 Multiple Interrupts

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.

3.5 Specific Interrupts

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.

3.5.2 Software Interrupt/Trap

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

unknown errors. The Motorola MC68HC705C8 has a software interrupt


executable instruction called SWI.

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

A TIMER interrupt occurs when a timer overflow is detected. For example, In


the Microchip PIC16C74 there is a TMR0 interrupt which is generated when
the TMR0 8 bit timer overflows. An overflow occurs when the timer goes from
1111 1111 to 0000 0000. The timer is usually incremented every instruction
cycle.
TIMER interrupts in general provide access to an external clock. This is useful
in applications where timing is critical. For control applications, for example, it
is important to sample input data at specific time intervals. This is usually
accomplished with TIMER based interrupts.
You can also use TIMER interrupts in other ways, depending upon your
hardware capabilities. Some chips, such as the Microchip PIC16C74, have
readable and writable timers which let you specify a certain duration of time.
Each instruction cycle of the CPU counts from this time and when the counter
overflows the TIMER interrupt fires. Other chips let you specify the number of
cycles as an interval for the TIMER and it will fire every time the specified
number of cycles pass.
The TIMER interrupt is most useful in building a watchdog or computer operating
properly timer for devices which do not include one. First you configure the
watchdog to tell it how long it can last without attention. Then, you provide
36
Power

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

Microcontrollers have an on-board circuit which provides brownout protection.


A brownout occurs when the operating voltage falls below the defined
brownout voltage. When a brownout occurs the device is reset and waits for the
operating voltage to rise above the brownout voltage.

3.6.2 Halt/Idle

Individual microcontrollers have specific modes which stop the execution of


the program without affecting the power to the microcontroller. In these
modes less power is required and power consumption is reduced. Halt mode
stops all activities and can be terminated by a reset or an interrupt. Idle mode
stops most activities. The clock, watchdog, and idle timer remain active.

3.7 Input and Output

Input and output are lines or devices which carry information between the
microcontroller and the outside world.

3.7.1 Ports

A port is a physical input/output connection. Most ports on 8 bit


microcontrollers are 8 bits or less. Ports can be either input, output or

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.

3.7.2 Serial Input and Output

CAN
Controlled Area Network was developed by Bosh and Intel. It is a multiplexed
wiring scheme.

I2C™ (Inter-Integrated Circuit bus)


A two wire serial interface developed by Phillips. It is a multi-master, multi-
slave network interface with collision detection. Up to 128 devices can exist on
the network. The two lines consist of the serial data line and the serial clock line
which are both bidirectional..
It provides a communication link between integrated circuits. Every component
hooked up to the bus has its own unique address.

J1850
J1850 is the SAE (Society of Automotive Engineers) standard.

MICROWIRE PLUS (National Semiconductor)


A serial synchronous bi-directional communications interface used on National
Semiconductor devices. It is SPI compatible. It consists of an 8 bit serial shift
register with serial data input serial data output and serial shift clock

38
Input and Output

SCI (Serial Communications Interface)


A Serial Communications Interface is an asynchronous serial interface. It is an
enhanced UART. The SCI has a transmitter and a receiver which are
functionally independent but use the same data format and baud rate.
SCI features standard nonreturn to zero format, error detection, simultaneous
send and receive, 32 different baud rates, selectable word length, and four
separate interrupt conditions. There are five registers: SCDAT (serial
communication data register), SCCR1 (serial communication control register 1),
SCCR2 (serial communication control register 2), SCSR (serial communication
status register), and the baud rate register.

SPI (Serial Peripheral Interface)


A Serial Peripheral Interface is a three-wire synchronous serial port which
allows several microcontrollers to be interconnected. In the configuration there
must be at least one microcontroller master while the remaining
microcontrollers can either be masters or slaves.
SPI features four programmable master bit rates, programmable clock polarity
and phase and end of transmission interrupt. The clock is not included in the
data stream and must be provided as a separate signal. There are three registers,
SPSR, SPCR and SPDR that allow for control, status and storage functions.
There are four basic pins which have different names on different devices:
 Data out
 Data in
 SCK (Serial Clock)
 SS (Slave Select)
A SPI is a type of SSP.

SSP (Synchronous Serial Port)


The SSP does not require start and stop bits and operates at higher clock rates
then asynchronous serial ports.

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.

3.8 Analog to Digital Conversion

It is often necessary to convert an external analog signal to a digital


representation or to convert a digital signal to an analog signal.

Successive Approximation Converter


Most microcontrollers use a successive approximation A/D converter. The
converter works with one bit at a time from the MSB (most-significant bit) and
determines if the next step is higher or lower. This technique is slow and
consumes a great deal of power. It is also cheap and has consistent conversion
times.
The Microchip PIC16C74 has an A/D converter module which features 8
analog inputs. These 8 inputs are multiplexed into one sample-and-hold which
is the input into the converter.

Single Slope Converter


Appears in National Semiconductor’s COP888EK. It includes an analog
MUX/comparator/timer with input capture and constant current source. The
conversion time varies greatly and is quite slow. It also has 14 to 16 bit
accuracy.

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

3.9.1 Digital Signal Processor

A Digital Signal Processor (DSP) runs repetitive, math intensive algorithms.

3.9.2 Clock Monitor

The clock monitor watches the clock and determines if it is running too slow. It
can activate a microcontroller reset.

3.10 Devices

3.10.1 Mask ROM

ROM whose contents are set by masking during the manufacturing process.

3.10.2 Windowed Parts

A microcontroller with a window which allows for ROM contents to be erased.

3.10.3 OTP

OTP (One Time Programmable) devices are microcontrollers where once a


program is written into the device it cannot be erased.

41
4. Programming Fundamentals
It is necessary to understand some basic computer programming concepts
before learning C programming.

4.1 What is a Program?

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.

4.2 Number Systems

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

Base 10 Decimal Base 2 Binary Base 16 Hexadecimal


0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F
16 0001 0000 10
17 0001 0001 11
100 0110 0100 64
255 1111 1111 FF
1024 0100 0000 0000 400
65,535 1111 1111 1111 1111 FFFF
Table 6: Binary, decimal and hexadecimal

4.3 Binary Information

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

A computer’s memory consists of a long series of switches known as bits.


These switches can exist in only two states; therefore, they are well suited to
the binary alphabet. At any given time a single bit in computer memory can
represent either 1 or 0. A bit containing 1 is referred to as being set while a
bit containing a 0 is referred to as being unset or clear. Anything that a
computer reads, writes, or executes must be encoded as a series of set and
unset bits.
The following diagram shows the relationship between data value and data
storage:
Bit Number 7 6 5 4 3 2 1 0
Bit Values 1 1 0 1 1 1 0 1 Address 0x00

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 1’s and 0’s. 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

4.4 Memory Addressing

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.

4.5 Machine Language

Computers only understand one language: machine language. Each family of


computers has its own machine language which can not be understood by any
other family of computers. Any particular computer within a family may also
use a slightly different dialect of the family language and may incorporate
features not available to other members of the family. Any instructions for a
specific computer must be given in its individual machine language. Machine
language is a collection of binary numbers such as:
00000000000001010101011110100110100010101010011010001111101
11100000010000000000011111111100011101010110000000000000010
10
The hexadecimal equivalent is:
000557A68AA68FBC0800FF8EAC000A

4.6 Assembly Language

Each microcontroller has its own assembly language or assembly language


variation. Assembly language consists of mnemonic instructions and addressing
modes where the instruction describes what to do and the addressing mode
describes where to do it. The following instructions are from the National
Semiconductor COP888 assembly language:

46
Instruction Sets

Address Instruction Hex Explanation


0005 LD B,#034 9F 34 Load B register with 34
0007 SBIT 06,[B] 7E Set bit 6 of B register to 1
000E IFBIT 06,[B] 76 If bit 6 of B register is 1 then execute
next instruction
0011 LD A,[B] AE Load Accumulator with contents of
location referenced by B
0012 IFNE A,#001 99 01 If the Accumulator is not equal to 1
then execute next instruction
0014 JP 00017 02 Jump to PC + 02 +1 (0017)
0015 CLRA 64 Clear accumulator
0000 JMPL 00005 AC 00 05 Jump to address 0005
Table 7: Interpretation of assembly language

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

Assembly language programs are not directly executable, they must be


translated to machine language. This translation is done using a program called
an assembler.

4.7 Instruction Sets

Most microcontrollers use the CISC (Complex Instruction Set Computer)


foundation. CISC is an architecture which handles complex instructions. If one
complex instruction encapsulates several simple instructions, the time spent
retrieving the instruction from memory was reduced. This is useful with
sequential computing designs.
Some microcontrollers are based upon a RISC (Reduced Instruction Set
Computer) design. RISC is an architecture which handles simple instructions.

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

4.8 The Development of Programming Languages

Programming languages were originally developed to reduce program


development time. Programming languages also increase the portability,
readability and modifiability of programs. For example a program written for
the National Semiconductor COP8SAA7 in its assembly language will not run
on the Motorola 68HC705C8, actually it may not run on other COP8 parts
because of differences in the instruction set. If a program is written in C for the
COP8SAA7 it can be ported to the 68HC705C8 with few changes.
The following examples show the same C code compiled for the COP8SAA
and the 68HC705C8. When a language such as C is used the program must
simply be recompiled while an assembly language program must be completely
rewritten.
0034 0006 bit [email protected];
0008 char j;
0005 #define bit5 5
0008 0005 bit bj@&j.bit5;
0008 0005 bit [email protected];
void main(void){
0005 9F 34 LD B,#034 b=1;
0007 7E SBIT 06,[B]
0008 BD 08 6D RBIT 05,008 bj=0;

48
The Development of Programming Languages

000B BD 08 7D SBIT 05,008 bj1=1;


000E 76 IFBIT 06,[B] if (b==1)
000F 6E RBIT 06,[B] b=0;
000F 6E RBIT 06,[B]
0010 57 LD B,#08 b=(j==1)?0:1;
0011 AE LD A,[B]
0012 99 01 IFNE A,#001
0014 02 JP 00017
0015 64 CLRA
0016 02 JP 00019
0017 98 01 LD A,#001
0019 9F 34 LD B,#034
001B 92 00 IFEQ A,#000
001D 02 JP 00020
001E 7E SBIT 06,[B]
001F 01 JP 00021
0020 6E RBIT 06,[B]
0021 8E RET }
Example 4: A typical assembly language program for the COP8SAA
0034 0006 bit [email protected];
0050 char j;
0005 #define bit5 5
0050 0005 bit bj@&j.bit5;
0050 0005 bit [email protected];
void main(void){
0200 1C 34 BSET 6,$34 b=1;
0202 1B 50 BCLR 5,$50 bj=0;
0204 1A 50 BSET 5,$50 bj1=1;
0206 0D 34 02 BRCLR 6,$34,$020B if (b==1)
0209 1D 34 BCLR 6,$34 b=0;
020B B6 50 LDA $50 b=(j==1)?0:1
020D A1 01 CMP #$01
020F 26 03 BNE $0214
0211 4F CLRA
0212 20 02 BRA $0216
0214 A6 01 LDA #$01
0216 4D TSTA
0217 26 04 BNE $021D
0219 1D 34 BCLR 6,$34
021B 20 02 BRA $021F
021D 1C 34 BSET 6,$34
021F 81 RTS }
Example 5: Program in Example 4 compiled for the 68HC705C8

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

Compilers translate high level programming language instructions into machine


language. They perform the same task for high level languages that an assembler
performs for assembly language, translating program instructions from a
language such as C to an equivalent set of instructions in machine language.
This translation does not happen in a single step – three different components
are responsible for changing C instructions into their machine language
equivalents. These three components are:
1) Preprocessor
2) Compiler
3) Linker

4.9.1 The Preprocessor

A program first passes through the C preprocessor. The preprocessor goes


through a program and prepares it to be read by the compiler. The
preprocessor includes the contents of other programmer specified files,
manipulates the program text, and passes on instructions about the particular
computer for which the compiler will be translating.

4.9.2 The Compiler

The compiler translates a program into an intermediate form containing both


machine code and information about the program’s contents. The compiler is
the second component to handle your program. The compiler has the most
important job: digesting and translating the program into a language readable by
the destination computer.
Many compilers operate in different passes through the code. There are often
passes specifically to handle optimizations of code which will reduce the size of
the machine code generated.

4.9.3 The Linker

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.

4.10 Cross Development

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.

4.10.1 Cross compiler

An embedded systems developer writes and compiles programs on a larger


computer which can support a C development environment. The compiler used
does not translate to the machine language of the development computer, it
produces a version of the program in the machine language of the 8 bit
microcontroller. A compiler that runs on one type of computer and provides a
translation for a different type of computer is called a cross-platform
compiler or cross-compiler.
The object code formats generated by a cross-compiler are based on the target
device. For example, a compiler for the Motorola MC68HC705C8 could
generate an S-record file for its object code.

4.10.2 Cross development tools

After a program is compiled it must be tested using a simulator or an


emulator. After testing the developer uses a special machine called a

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 microcontroller’s 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.

4.10.3 Embedded Development Cycle


The development process for embedded software follows a cycle:
1. Problem specification
2. Tool/chip selection
3. Software plan
4. Device plan
5. Code/debug
6. Test
7. Integrate

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 wait(registera); //wait function prototype

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

It is not necessary to understand the specifics of the sample program at this


point. It is more important that you become familiar with some of the basic
concepts involved in C program development.
The following sections provide a general explanation of the C program in
Example 6.

5.1 Program Comments

A good programmer includes comments throughout a program. Comments


help to explain what the code is doing at a particular point and often state what
specific symbols or operations represent.
C compilers use slash and asterisk combinations as comment delimiters. When
the compiler encounters a slash immediately followed by an asterisk , /*, it
treats every character following this pair as a comment until an asterisk
immediately followed by a slash, */, is encountered.
Most modern C compilers also accept C++ comment syntax. If the compiler
reaches a slash immediately followed by another slash, //, in the source code it
treats the rest of that line as a comment. The C++ convention is more readable
and easier to debug because the effect of the comment syntax does not carry
over from one line to the next as in traditional C.
All comments in code examples provided throughout this book use C++ style.
If you have a compiler which does not support this comment syntax, you must
replace every // with /* and place */ at the end of the comment.

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.

5.2 Preprocessor directives

Example 6 contains three preprocessor directives: #include, #define,


and #pragma. Preprocessor directives are specific instructions given to the
preprocessor. Preprocessor directives are always preceded by the # character
which is referred to as a hash mark. These directives are used as follows:

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.

#pragma portrw PortA @ 0x0A;


#pragma portw PortADir @ 0x8A;
The preprocessor handles #pragma directives in a slightly different fashion
than other preprocessor directives. #pragma directives instruct the compiler
to behave in a certain way based on the description of the hardware resources
of the target computer. #pragma statements are most often used in header
files which provide the hardware specifications for a particular device.
#pragma port directives, for example, describe the ports available on the
target computer. The description includes details on port location, whether they
are read, write or read/write and the names the program uses to access ports.
In the excerpt from Example 6 shown above, the compiler is informed that
two ports are available. The name PortA refers to physical port A’s data
register, which is available for reading and writing and is located at address
0x0A. The name PortADir refers to physical port A’s data direction register,
which is available for writing only and is located at address 0x8A.

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.

5.3.1 The main( ) function

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.

5.3.2 Calling 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.

5.4 The Function Body

Every function definition has a function header. A function header describes


what type of value the function returns, the name of the function, and what
input arguments it expects. The body of the function follows the function
header. The function body contains a set of statements between braces which
are executed when the function is called. There are several different types of C
statements.

5.4.1 The Assignment Statement

One of the simplest and most common statements in C is the assignment


statement. An assignment statement takes the value of the expression on the
right of the equal sign and assigns it to the symbol on the left side of the equal
sign. For example:
PortADir.0 = OUTPUT;
PortADir.1 = INPUT;
Example 8: Using the C assignment statement

In Example 8 the symbols PortADir.0 and PortADir.1 refer to the


first two bits of the port associated with the name PortADir.
The first statement assigns the numeric value of the expression on the right of
the equal sign to bit 0 of PortADir, which represents the port A direction
register. From the #define directives we know that OUTPUT is really a
symbolic constant associated with the value 0. Therefore, this assignment
statement clears bit 0 of the port A direction register.
By contrast, the second assignment statement sets bit 1 of the port A direction
register. How? Recall that INPUT is a symbolic constant associated with the
value 1 in a #define statement.

5.4.2 Control statements

Control statements allow decisions to be made to determine which statements


are executed and how often. For example, suppose you need to write a set of
instructions for making coffee in a coffee maker. The amount of water you

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

The if() statement provides the ability to make decisions. If the if


statement condition is true then the computer executes the statements in the
if body. In Example 9, the value of PortA.1 is compared with the value of
PUSHED, if data bit number 1 is set (has the value 1) then the program will
execute any statements in the if body. The body statement sets port A data
bit number 0 by assigning it the value of ON.
61
First Look at a C Program

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.

5.4.3 Calling Functions

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.

5.5 The Embedded Difference

Several things make the program in Example 6 typical of embedded systems


programs in C.

62
The Embedded Difference

5.5.1 Device Knowledge

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.

5.5.2 Special Data Types and Data Access

Embedded systems developers require direct access to registers such as the


accumulator. In Example 6 the wait() function is called with an argument
of type registera. This is a special type which represents the accumulator.
Embedded developers are much closer to their target hardware then other
programmers. They often access and control the basic hardware of the device
they are programming.

5.5.3 Program Flow

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.

5.5.4 Combining C and Assembly Language

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);
}

//function with inline assembly


void wait(registera){
char temp, time;
// ocap_low and Ocap_hi are the output compare register
//this register is compared with the counter and the ocf
//bit is set (bit 6 of tim_stat)
#asm
STA time ;store A to time
LDA #$A0 ;load A with A0
ADD ocap_low ;add ocap_low and A
STA temp ;store A to temp
LDA #$25 ;load A with 25
ADC ocap_hi ;carry + ocap_hi + accumulator
STA ocap_hi ;store A to ocap_hi
LDA temp ;load temp to accumulator
STA ocap_low ;store a to ocap_lo
LOOP BRCLR 6,tim_stat,LOOP ;branch if OCF is clear
LDA ocap_low ;load ocap_lo to A
DEC time ;subtact 1 from time
BNE LOOP ;branch if Z is clear
#endasm
}
Example 12: C functions containing inline assembly language

5.5.5 Mechanical Knowledge

Techniques used in an embedded system program are often based upon


knowledge of specific device or peripheral operation. For example, Example 6
calls the wait() function with a value of 1 after it has detected that the
button is pushed and then checks to see if the button is still pushed. The code
is written in this manner to deal with the issue of contact bounce.
When a button is pressed it “bounces” which means that it is read as several
pushes instead of just one. It is necessary to include debouncer support in order
to ensure that a real push has occurred and not a bounce. The wait() function
creates a delay before the button is checked again. If the button is no longer in
a pushed state then the push is interpreted as a bounce and the program waits
for a real push.

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

6.1 C Preprocessor Directives

The simple C program shown in Example 6 in the previous section introduced


several preprocessor directives:
 #include directives include the contents of another file
 #define directives define symbolic constants
 #pragma directives describe details of the target hardware
Section 13, The C Preprocessor, provides a detailed explanation of the
preprocessor.

6.2 Identifier Declaration

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

memory. At a given memory location the computer can store a value, or a


group of program instructions.

6.2.1 Identifiers in Memory

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.

6.2.2 Identifier names

An identifier name can be any word beginning with a letter or underscore


character. The rules for naming identifiers are quite straightforward. An
identifier can be almost any word that begins with a letter or underscore
character, followed by 0 or more letters, numbers or underscore characters.

 An identifier can not be a C keyword


The C language has keywords which the compiler reserves because they have
special meaning in the language. For example, the word if is used to signify
the beginning of a decision block. A keyword may not be used as an identifier
name. Some standard keywords in C are:
auto default if short union
break do int signed unsigned
case else long static void
char enum main struct volatile
const extern pointer switch
continue for return typedef
Example 13: Common C keywords

Compilers that provide special enhancements or extensions to the language will


add keywords to this list, so you must check the documentation for your
particular compiler to find out what other words not to use for identifier
names.

66
Identifier Declaration

 Identifiers only have certain significant characters


Most compilers support identifier names of at least 31 characters in length. This
allows you to use precise and meaningful names for variable and function
names. However, to conserve memory a compiler will often only consider some
characters in an identifier name as significant. This means that two identifiers
which may seem different are treated as the same symbol by the compiler. For
example, a compiler which only considered the first 5 characters of an identifier
as significant would treat the following two identifiers as if they were the same
symbol:
PortADir
PortA

Notice that even though the two identifiers are different words, the first five
significant characters are identical.

6.2.3 Variable Data Identifiers

Identifiers which represent variable data values, called variables, require


portions of memory which can be altered during the execution of the program.
The compiler will allocate a block of its data memory space, usually in RAM,
for each variable identifier.
For example, the declaration int currentTemperature; for the
variable currentTemperature will cause the compiler to allocate a single
byte of RAM.
The keyword int in the variable declaration tells the compiler that
currentTemperature will contain an integer value and will require a
single byte of RAM to contain this value.

6.2.4 Constant Data Identifiers

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;

This declares a variable called maximumTemperature and sets its initial


value to 30. The keyword const tells the compiler that the identifier is a
constant and that a single byte in ROM should be reserved to contain the value
30. When the identifier maximumTemperature is used in the program it
refers to the memory location in ROM which contains the value 30.

6.2.5 Function Identifiers

Function identifiers are not altered during program execution. Once the value
of a function has been written in the computer’s 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 function’s 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

When a program runs it executes program statements. Declarations describe the


memory locations which statements can use to store and manipulate values.
The most frequently used statement in any programming language is the
assignment statement. C provides many different ways to construct an
assignment statement; however, the following example shows the simplest way:
currentTemperature = 20;

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

values are stored in machine program memory space.

68
Statements

6.3.1 The Semicolon Statement Terminator

All statements in C must end with a semicolon. C uses the semicolon as a


statement terminator5. One of the most common errors in C programming is
an extra, missing or misplaced semicolon. If you leave out a semicolon the C
compiler will not know where a statement should end.
For example, suppose you wrote the following two statements. The compiler
would produce an error. Why?
currentTemperature = 20
currentTemperature = 25;

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.

6.3.2 Combining Statements in a Block

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

5Unlike languages PASCAL-like where the semicolon is used as a statement


separator.

69
C Program Structure

} // this brace closes the block for while


Example 14: Using braces to delineate a block

The general format for the while statement looks like:


while (condition) statement;

However, since you can substitute a statement block anywhere a single


statement can occur, the most commonly used form of the while statement
looks like:
while (condition){
statements
}
Example 15: The while loop

It is good programming practice to use braces whenever you use a loop or


conditional construct such as while and if, even with a single statement
block. The braces ensure that anyone reading your program code can tell exactly
which statements belong to the while or if.

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.

7.1 The ASCII Character Set

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.

7.2 Data types

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.

7.3 Variable Data Types

When you declare an identifier used in your program, either as a variable or a


function, you specify a data type as part of the declaration. The compiler will
allocate the appropriate amount of computer memory for use with each
identifier.
It is possible to declare a number of variables of the same type in the same
declaration by including a list of identifier names separated by commas. Good
programmers will most often use this method for declaring a group of variables
that serve a similar function within the program. A typical case is a group of
counters the programmer will use to regulate the control of program flow
through loops:
int currentTemperature;
char tempScaleUsed;
long TempDifference;
int count1, count2, count3;
Example 16: Declaring variable types

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.

7.3.1 Variable Data Type Memory Allocation

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 machine’s 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.

7.3.2 Variable Scope

Not all parts of a program recognize declared variables. The visibility of a


declared variable is called the variable’s scope. If a portion of a program lies
outside a variable’s scope then the compiler will give an error if you refer to the
variable in that portion. The scope of a variable includes the locations in a
program where the variable is a recognized and meaningful symbol. Outside
that scope the variable is an unknown or undefined symbol.

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

7.3.3 Global Scope

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 variable’s 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.

7.3.4 Local Scope

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.

7.3.5 Declaring Two Variables with the Same Name

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.

7.3.6 Why Scope is Important

Why is scope an important concept? It can provide tangible benefits to


programmers.
Since C is a function-oriented language where programs are built from
collections of functions, variable scope promotes data abstraction. Variables
declared inside a function remain local to that function only. Other functions in
the program can use identical local variable names without creating conflicts.
This means that you can use functions in your program and only know about
the function interface. It is not necessary to see inside a function to use it in a
program, it is only necessary to know what to pass in and what will be returned.
Data abstraction allows a programmer to create a function which others can
make use of the without seeing the function source code. This may sound
dangerous but all C compilers take advantage of this principle. The standard
library functions available with all C compilers depend upon data abstraction to
be useful – programmers include standard library functions in their code all the
time without worrying about potential variable name conflicts.

7.4 Function Data Types

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

7.4.1 Function Parameter 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.

7.5 The Character Data Type

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.

7.5.1 Assigning a character value

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

You might also like