📄 cstart.htm
字号:
<!doctype html public "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<TITLE>EMBEDDED SYSTEMS</TITLE>
<META NAME="GENERATOR" CONTENT="Internet Assistant for Word 1.00">
<META NAME="AUTHOR" CONTENT="Brian Brown">
</HEAD>
<BODY>
<CENTER><H2>Embedded Code Software</H2></CENTER>
<CENTER><B>Copyright Brian Brown, 1989-1995</B></CENTER>
<A HREF="default.htm"><IMG SRC="images/menu.gif" ALT="menu"></A>
<A HREF="swparta.htm"><IMG SRC="images/previous.gif" ALT="prev"></A><BR>
<HR>
<B>INTRODUCTION</B><BR>
Target systems are generally TURNKEY, which means that when the computer
is powered on the software automatically runs without user intervention.
<P>
This implies that there is possibly no operating system present, rather,
the software contains all the routines necessary for correct operation of
the system.
<P>
In designing software for these types of applications, it is important
to design the software in such a way as to perform the necessary hardware
device initializations, and to handle shutdowns and error situations
gracefully.
<P>
<HR>
<B>HARDWARE CONSIDERATIONS</B><BR>
The processor, upon a reset or power on situation, gets the start address
of the program from a RESET VECTOR.
<P>
The software stored in ROM must contain this vector. Other tasks the
software must do is,
<UL>
<LI>test the RAM, ROM and CPU
<LI>initialize any hardware peripheral chips
<LI>test error/interrupt circuitry and exception handlers
</UL>
<P>
<HR>
<B>TESTING ROM,RAM and CPU</B><BR>
Testing the ROM can be achieved by a simple checksum loop. A spare
location in the ROM can be used to store its checksum.
<P>
When the target system is turned on, it computes its own ROM checksum
then compares it with that stored in ROM. If these checksums are the
same, the program can then continue.
<PRE>
checkrom: xor ax, ax ; clear ax
mov ds, 0f000h ; start at location 0 in EPROM
mov si, 0 ; offset 0
mov cx, 0ffffh ; number of characters in checksum
chklp1: add ax, ds:[si] ; add character to total
inc si ; point to next character
loop chklp1
mov si, chksum ; now compare with rom chksum
cmp ax, ds:[si]
jne rombad
next:
org 0ffffh ;top of 32k eprom 27C256
chksum: db 04h ; calculated checksum
</PRE>
Testing RAM can be done by writing values to the RAM areas and then
verifying that the read-back value is the same.
<P>
It is vital that these routines are written in such a way that they do
not rely upon the use of a STACK, ie, using CALLS, as a RAM error will
cause the system to crash upon return from the test module.
<P>
The best patterns to use are AA and 55 as these test for flawed bits
within each byte.
<P>
In most systems it will be necessary to initialize the dynamic RAM refresh
subsystem before testing the RAM. The IBM-PC uses a timer counter chip
(8239, channel 0), tied to a direct memory access chip (8253) to refresh
the dynamic RAM by pseudo-reads.
<P>
RAM chips normally go bit faulty, in that one or more bits fail to
assume the correct logic state. Using the patterns AA and 55 test for
these faulty bits. There are more comprehensive memory tests available,
<UL>
<LI>walking ones
<LI>walking zeros
<LI>checker board
</UL>
<P>
Which test is used is dependent upon how critical the application is.
More comprehensive tests can take long periods to complete.
<PRE>
ramtest: mov ds, 0h ; RAM start 00000h
mov cx, 4000h ; test 16k RAM
mov si, 0
mov dx, 0AA55h
ramlp1: mov al, dh
mov byte ptr ds:[si], al ; write value
cmp al, byte ptr ds:[si] ; read back and compare
jne ramerr
mov al, dl
mov byte ptr ds:[si], al ; write value
cmp al, byte ptr ds:[si] ; read back and compare
jne ramerr
inc si
loop ramlp1
</PRE>
Testing the CPU can be performed by loading the registers with values
and doing known calculations, then testing the register result against
the expected result (using the condition code register).
<PRE>
cputest: cli ; disable interrupts
mov ah, 0d5h ; set sf, cf, zf, and af
sahf
jnc cpuerr ; test carry
jnz cpuerr ; test zero
jnp cpuerr ; test parity
jns cpuerr ; test sign
lahf ; transfer flags to ah
mov cl, 5 ; test auxillary carry
shr ah, cl ; rotate ah
jnc cpuerr
mov al, 40h ; test overflow flag
shl al, 1
jno cpuerr
xor ah, ah
sahf ; clear sf, cf, zf, pf
jc cpuerr
jz cpuerr
js cpuerr
jp cpuerr
lahf
mov cl, 5
shr ah, cl
jc cpuerr
shl al, 1
jo cpuerr
regtest: mov ax, 0ffffh
mov ds, ax
mov bx, ds
mov es, bx
mov cx, es
mov ss, cx
mov dx, ss
mov sp, dx
mov bp, sp
mov si, bp
mov di, si
or ax, di
jnz cpuerr
</PRE>
Modern processors such as the iAPX386 perform self tests at power on,
with EAX holding 00000000h to indicate success. Register DX holds the
Component and ID Revision numbers of the 386 chip.
<P>
The preferred order of testing is CPU, ROM then RAM.
<P>
In large memory systems, a parity generator is probably used. This
requires either 0's or 1's to be written to the memory in order for
the parity comparator system to work properly (the PC requires 0's).
<P>
The parity system in the PC is connected to the NMI interrupt line,
so this may require a dummy service routine whilst the parity system
is being initialized.
<P>
Normally, the NMI is disabled by writing to port A0h.
<PRE>
nmioff: mov al, 0
out 0a0h, al
</PRE>
<HR>
<B>INITIALIZE HARDWARE PERIPHERAL CHIPS</B><BR>
Typical hardware devices are
<UL>
<LI>Parallel interface chips (8255)
<LI>Serial interface chips (8250)
<LI>Priority interrupt controllers (8259)
<LI>Real time clocks etc
</UL>
<P>
The first step is obtaining data sheets for each of the chips then
writing initialization routines as HLL algorithms. These can then
be compiled or converted to assembler.
<P>
Determine the order in which the devices must be initialized, system
chips like PIC's may need to done first. PROBABLY, the PIC and timers
would be disabled till all the other hardware is initialized, at which
time they would be re-activated.
<P>
You can't have interrupts being generated whilst setting up the system!
The hardware should also be tested using dummy interrupts. Serial
systems can be tested using LOOP BACK MODE, parallel systems for
handshake signals.
<P>
Spare bits on parallel ports could be used for system checkout.
<P>
<HR>
<B>INTERRUPTS/EXCEPTION HANDLING, ERROR RECOVERY</B><BR>
Interrupts can be tested using a dummy interrupt routine, the address
of which is placed into the associated vector location. An interrupt
is then forced and the dummy routine checks to see if the interrupt
call worked.
<PRE>
; irq vectors are located in RAM and initialised at power-up
org 0004h ; irq 1
dw dummyint
CSEG
dummyint: mov ax, 0h
iret
testint: mov ax, 0ffh
swi 1
cmp ax, 0
jne interr
ret
</PRE>
Unexpected interrupts can also be tested for by initialising all
interrupt vectors to the dummy interrupt handler, and waiting for
a small delay.
<PRE>
testall: mov ax, 0ffh
sti ; enable interrupts
sub cx, cx
tstlp1: loop tstlp1 ; wait for 1 second
tstlp2: loop tstlp2
cmp ax, 0ffh ; any interrupts occur?
jne interr
</PRE>
Exception handling deals with the occurrence of unexpected
hardware/software errors. These could be interrupts, overflows/wrap
around, divide by zero, power down etc. All possible foreseeable
exceptions should be catered for by software, or disabled by the
use of hardware. This could be simple IRET statements.
<P>
Some systems use watchdog timers to prevent software runaway. A watchdog
timer is programmed to generate an interrupt at regular intervals (say
every 20milliseconds). The timer is loaded with a count value and
decrements by one for each system clock period (or a divisor thereof).
The main software periodically resets the timer (every 15ms) to prevent
the interrupt occurring.
<PRE>
main()
{
reset_timer(1000);
for( ; ; ) {
.......
.......
reset_timer(1000);
.......
.......
reset_timer(1000);
}
}
</PRE>
When the software hangs, the timer will not be reset, thus generating an
interrupt. The service routine will then either recover, restart, or
shut the system down.
<P>
Error recovery means handling exceptions in such a way as to continue
processing with little or no degrading of performance. It is best to
incorporate several layers of error recovery so that a system is said to
'degrade gracefully'.
<P>
It should not fail instantly. Certain techniques used could be switching
to an alternate RAM area, using timeouts and retrying (for parallel and
serial ports), performing calculations etc.
<PRE>
retry
re-initialize then retry
abort
</PRE>
<HR>
<B>WARM-RESTARTS AND BATTERY BACKED UP VARIABLES</B>><BR>
Often, a running program may crash. At this resetting the target
system would re-initialise the RAM and hardware, and make debugging
difficult.
<P>
It is easy to add simple warm restart capability to the target system
by using a variable located in RAM. At power-up, this variable is
tested for a specific value, and if set, this indicates a warm-start
is required.
<PRE>
warmboot: cmp warmbootflag, 1234h
je warmstart
mov warmbootflag, 1234h
warmstart:
</PRE>
<P>
<HR>
<B>SOFTWARE INITIALIZATION AND C PROGRAMS</B><BR>
Divide memory into segments holding CODE, DATA and STACK areas. On
startup, the DATA segment should be zeroed out and any initialized
variables have their values copied from ROM into RAM.
<P>
The processor stack registers is initialized to the STACK
segment (SS and SP), and the processor data registers to the
DATA segment (ES and DS).
<P>
Some compilers have special pre-processor directives to simplify
segment placement, eg, the CC09 supports #DATA, #CODE and #CONST,
which specify segment types to the assembler/linker utilities.
<P>
<HR>
<B>THE STRUCTURE OF C PROGRAMS</B><BR>
A typical C program comprises the following segments
<PRE>
_TEXT Code
_STACK Stack
_BSS Un-initialized data, eg, static int i;
_DATA Initialized data, eg, static int y = 3;
</PRE>
NOTE: The IC86 compiler does not use underscores or the _BSS segment,
whereas both TurboC and MSC do. Standard convention is to prefix
underscores to variable and function names (except for the IC86 compiler).
<P>
The two data segments are combined forming a single concatenated
data segment called DGROUP. The C compiler inserts code to zero
out the _BSS segment before jumping to _main. Because the _BSS segment
contains zero's, it does not need to be stored in ROM.
<P>
The _DATA segment, containing initialized values, will need to be
appended at the end of the _CODE segment, and stored in ROM. During
startup, the _DATA segment will be copied to RAM, then the DS
register will be switched to the RAM area.
<P>
In creating rommable code, it is necessary to write your own
startup code which replaces that of the C compiler (TurboC uses
C0x.obj). The basic structure of the startup code is,
<UL>
<LI>initialize the DS and SS registers
<LI>zero out the _BSS segment
<LI>copy initialized data from ROM into RAM
<LI>switch DS register from ROM to RAM
<LI>jump to main
</UL>
<P>
The startup code is normally written in assembler, so that direct
control over segment placement can be achieved.
<P>
<HR>
<B>INITIALIZATION OF THE _STACK SEGMENT</B><BR>
The first thing the startup code will do is initialize the
stack registers to point to the _STACK segment.
<PRE>
; startup.asm
_STACK segment para stack 'STACK'
db 1024 dup ('STACK');
stackend label word
_STACK ends
_TEXT segment byte public 'CODE'
assume CS:_TEXT, DS:_DATA, ES:_BSS, SS:_STACK
start: cli ; disable interrupts
mov ax, _STACK ; initialize stack
mov ss, ax
mov ax, offset stackend
mov sp, ax
...
...
sti
call _main
jmp start
_TEXT ends
</PRE>
<HR>
<B>INITIALIZING THE _BSS SEGMENT</B><BR>
The entire _BSS segment will be zeroed out by the following code portion.
<PRE>
; startup.asm
_BSS segment para public 'BSS'
_BSS ends
_BSSEND segment byte public 'BSSEND'
public endbss
endbss label byte
_BSSEND ends
DGROUP group _BSS, _BSSEND
_TEXT segment byte public 'CODE'
assume CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:_STACK
start: cli ; disable interrupts
... ; initialize stack
mov ax, seg _BSS
mov es, ax
mov cx, offset DGROUP:endbss
mov di, 0
mov ax, 0
rep stosb ; write to ES:DI
...
sti
call _main
jmp start
_TEXT ends
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -