Embedded Code Software

Copyright Brian Brown, 1984-2000.
menu prev

INTRODUCTION
Target systems are generally TURNKEY, which means that when the computer is powered on the software automatically runs without user intervention.

This implies that there is possibly no operating system present, rather, the software contains all the routines necessary for correct operation of the system.

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.


HARDWARE CONSIDERATIONS
The processor, upon a reset or power on situation, gets the start address of the program from a RESET VECTOR.

The software stored in ROM must contain this vector. Other tasks the software must do is,


TESTING ROM,RAM and CPU
Testing the ROM can be achieved by a simple checksum loop. A spare location in the ROM can be used to store its checksum.

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.

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

Testing RAM can be done by writing values to the RAM areas and then verifying that the read-back value is the same.

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.

The best patterns to use are AA and 55 as these test for flawed bits within each byte.

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.

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,

Which test is used is dependent upon how critical the application is. More comprehensive tests can take long periods to complete.

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

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).
	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

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.

The preferred order of testing is CPU, ROM then RAM.

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).

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.

Normally, the NMI is disabled by writing to port A0h.

	nmioff:         mov     al, 0
			out     0a0h, al


INITIALIZE HARDWARE PERIPHERAL CHIPS
Typical hardware devices are

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.

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.

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.

Spare bits on parallel ports could be used for system checkout.


INTERRUPTS/EXCEPTION HANDLING, ERROR RECOVERY
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.
; 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

Unexpected interrupts can also be tested for by initialising all interrupt vectors to the dummy interrupt handler, and waiting for a small delay.
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

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.

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.

	main()
	{
	    reset_timer(1000);
	    for( ; ; ) {
		.......
		.......
		reset_timer(1000);
		.......
		.......
		reset_timer(1000);
	    }
	}

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.

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'.

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.

	retry
	re-initialize then retry
	abort


WARM-RESTARTS AND BATTERY BACKED UP VARIABLES>
Often, a running program may crash. At this resetting the target system would re-initialise the RAM and hardware, and make debugging difficult.

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.

	warmboot:       cmp     warmbootflag, 1234h
			je      warmstart
			mov     warmbootflag, 1234h
	warmstart:


SOFTWARE INITIALIZATION AND C PROGRAMS
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.

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).

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.


THE STRUCTURE OF C PROGRAMS
A typical C program comprises the following segments
	_TEXT   Code
	_STACK  Stack
	_BSS    Un-initialized data, eg, static int i;
	_DATA   Initialized data, eg,    static int y = 3;

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).

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.

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.

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,

The startup code is normally written in assembler, so that direct control over segment placement can be achieved.


INITIALIZATION OF THE _STACK SEGMENT
The first thing the startup code will do is initialize the stack registers to point to the _STACK segment.
; 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



INITIALIZING THE _BSS SEGMENT
The entire _BSS segment will be zeroed out by the following code portion.
; 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


The purpose of the BSSEND segment is to provide a means of determining how long _BSS is. As BSSEND occurs after _BSS, then zeroing everything from _BSS to _BSSEND will ensure that all of _BSS is zeroed out!


INITIALIZATION OF THE _DATA SEGMENT
The _DATA segment must be copied to RAM before execution of _main() begins.
_DATA           segment para public 'DATA'
_DATA           ends

_DATAEND                segment para public 'DATAEND'
		public enddata
enddata         label   byte
_DATAEND                ends

DGROUP  group   _DATA, _DATAEND, _BSS, _BSSEND

_TEXT           segment byte public 'CODE'
_TEXT           ends

_TEXTEND                segment para public 'CODEEND'
		public  codeend
		db  16  dup (?)
codeend         label   byte
_TEXTEND                ends

CGROUP  group   _TEXT, _TEXTEND

_TEXT           segment
		assume CS:_TEXT, DS:DGROUP, ES:DGROUP, SS:_STACK
start:          cli                                     ; disable interrupts
		...                                     ; initialize _STACK
		...                                     ; initialise _BSS
		mov     ax, seg DGROUP
		mov     es, ax                          ; point ES to _DATA
		mov     cx, offset DGROUP:enddata
		mov     si, 0
		mov     di, 0
		assume ds:CGROUP:_TEXTEND
		mov     ax, seg _TEXTEND:codeend
		inc     ax
		mov     ds, ax                          ; point DS to _CONST
		rep     movsb                           ; copy _CONST to _DATA
		push    es                              ; point DS to _DATA
		pop     ds
		sti                                     ; enable interrupts
		call    _main
		jmp     start
_TEXT           ends


The option chosen by the LOCATE utility is to duplicate the _DATA segment into a segment called _CONST. This is then attached to the end of the _CODE segment and stored in EPROM.

The startup code then copies this _CONST segment to the address reserved in RAM for _DATA. It figures out the start address of the _CONST segment using the dummy _TEXTEND segment, and the length of the _DATA segment using the dummy segment _DATAEND.


ROMCODE AND THE TURBO C COMPILER
A sample startup code, written in assembler is
; tcstart.asm,  for f600:0000
extrn           _main:far

_text           segment byte    public 'CODE'
_text           ends
_textend                segment para    public 'CODEEND'
_textend                ends
_data           segment para    public 'DATA'
_data           ends
_dataend                segment para public 'DATAEND'
_dataend                ends
_bss            segment para    public 'BSS'
_bss            ends
_bssend                 segment byte    public 'BSSEND'
_bssend         ends
_stack          segment para    stack  'STACK'
_stack          ends
DGROUP          group   _DATA, _DATAEND, _BSS, _BSSEND
CGROUP          group   _TEXT, _TEXTEND

_TEXT           segment
		assume CS:CGROUP, DS:DGROUP, ES:DGROUP, SS:_STACK
start:          cli                ; disable interrupts
		mov     ax, _STACK ; initialize stack
		mov     ss, ax
		mov     ax, offset stackend
		mov     sp, ax  
		mov     ax, _BSS
		mov     es, ax
		mov     cx, offset DGROUP:enddata       ; calculate length of _BSS      
		mov     ax, offset DGROUP:endbss
		sub     ax, cx
		mov     cx, ax
		mov     di, 0
		mov     ax, 0
		rep     stosb   ; write to ES:DI
		mov     ax, seg DGROUP
		mov     es, ax  ; point ES to _DATA
		mov     cx, offset DGROUP:enddata
		mov     si, 0
		mov     di, 0
		assume ds:CGROUP:_TEXTEND
		mov     ax, seg _TEXTEND:codeend
		inc     ax
		mov     ds, ax  ; point DS to _CONST
		rep     movsb   ; copy _CONST to _DATA
		push    es      ; point DS to _DATA
		pop     ds
		sti             ; enable interrupts
		call    _main
		jmp     start
_TEXT           ends

_TEXTEND                segment
		public  codeend
		db  16  dup  (?)
codeend         label   byte
_TEXTEND                ends
_STACK          segment
		db      1024 dup ('STACK');
stackend                label   word
_STACK          ends
_DATAEND                segment
		public enddata
enddata         label   byte
_DATAEND                ends
_BSSEND         segment
		public  endbss
endbss          label   byte
_BSSEND         ends
end

The C source is compiled using the command line version of the compiler (tcc), then linked (using tlink) with the startup code and any user supplied libraries or object files (doit.bat).
	tasm /mx tcstart
	tasm /mx tclib
	tcc -a- -c -f- -G- -K -B -ml -M -N- -O- -r- -v- -y- -Z- -S -O- %1.c
	tlink /m tcstart %1 tclib, %1, %1
	locate %1
	hexbin2  %1.hex  %1.bin  i  f600

The LOCATE utility (developed by R Naro, Dr Dobbs, Dec 1987) is then used to process the .EXE file and assign physical addresses to each of the segments. This creates a .HEX file which is then used to burn an EPROM. The locate utility uses a special configuration file which specifies where the segments are to be located.


ROMCODE AND THE IC86 INTEL C COMPILER
The ic86 compiler generates the following segments using a LARGE memory model,
        
	filename_CODE
	filename_DATA
	filename_CONST          ; if using the RAM directive
	STACK

It does not prefix variable names, segments or function names with an underscore. If the compiler option ROM is specified, all constants and string literals are placed into the filename_CODE segment.

The startup code declares the segments which are used to arrange them in the correct order in physical memory. Initialized constants are placed in the FILENAME_CONST segment (when using RAM, else they are placed in the filename_CODE if using the ROM directive).

All other data is placed into FILENAME_DATA. This creates a small problem in referencing data, so it is best to local variables and pass them as parameters to functions which access them (ie, do not use GLOBAL variables!).

Initialized global variables should be avoided if designing programs for ROM. The compiler generates an error if it encounters initialized data, eg,

        
	static int y = 7;
	static int z;
compiled with the command line
        
	ic86 file.c LARGE ROM
will generate an error for the initialization of the variable y. As the DATA segment will be placed in RAM, any initial values will be lost.

The compiler also inserts code to initialize the DS register to the FILENAME_DATA segment. The following code will be used with the IC86 compiler.

; c86start.asm
name    c86start

extrn           main : far

STACK           segment para stack 'STACK'
		public top
		db      1024 dup ('STACK')
top             label   word
STACK           ends

CODE            segment byte public 'CODE'
		assume  cs:CODE, SS:STACK
start           proc    far
entry:          cli
		mov     ax, STACK
		mov     ss, ax
		mov     ax, offset top
		mov     sp, ax
		sti
		call    main
		jmp     entry
start           endp
CODE            ends
end

The command line sequence to generate the file is,
	asm86           c86start.asm
	ic86            test.c LARGE
	link86          c86start.obj,test.obj,c86clib.obj to test.lnk nobind
	loc86           test.lnk order(classes(stack,data,code)) &
	>> order(segments(STACK,TEST_DATA,CODE,TEST_CODE)) &
	>> ad(sm(stack(10000h),test_data(20000h))),(code(0f6000h)))
	oh86    test to test.hex

The segment maps look like,
 
C Program                       c86start
+---------+                     +---------+
|         |FILENAME_CODE        |+++++++++|STACK
|         |                     |+++++++++|
|         |                     |+++++++++|
+---------+                     +---------+
|         |FILENAME_DATA        |/////////|CODE
|         |                     |/////////|
+---------+                     +---------+
|+++++++++|STACK
+---------+

Combined segments after linking and locating are,
+---------+
|+++++++++|STACK (10000h)
|+++++++++|
|+++++++++|
+---------+
|         |FILENAME_DATA (20000h)
|         |
+---------+
|         |
|         |
+---------+
|/////////|CODE (0F6000h)
|/////////|
+---------+
|         |FILENAME_CODE
|         |
|         |
+---------+
+---------+



GENERATING ROMCODE FOR PC/XT AT F600:0000 USING TURBO-C
There is an empty ROM slot on some PC/XT compatibles. This slot is normally used for BASIC on IBM machines. It is memory mapped to segment 0F600h. Using this ROM space has the following advantages,

When the PC is powered up, it first attempts to boot from floppy disk. If this is unsuccessful, it reads the keyboard then generates an int18h instruction. This normally performs the following instruction,

	JMP     F600:0000

However, generating code to fit into this empty space requires special libraries or run time routines. Compilers like TC and MSC have libraries which use DOS in order to perform many functions. Since our turnkey system does not have DOS loaded, any systems we design using these libraries will certainly result in a system crash.

To overcome this, we shall design our own library of routines which emulate functions found in TC. These routines will look like functions in C, but will be written in assembler. First, lets look at creating a header file for these calls.


/* CROM.H, designed by SE2, 1990 for EMBEDDED CODE */

struct eightbit
{
   unsigned char al, ah, bl, bh, cl, ch, dl, dh;
};

struct sixteenbit
{
   unsigned int ax, bx, cx, dx, si, di, cflag;
};

union REGS
{
   struct sixteenbit x;
   struct eightbit   h;
};

/* function prototypes follow */
extern void outportb( unsigned int, char);
extern char inportb( unsigned int );
extern void int86( int, union REGS *, union REGS * );
extern void enable( void );
extern void disable( void );
extern void setvect( int, void (*ptr)( void ) );

The _int86 call provides a method of interfacing to the existing ROM BIOS routines, which are accessed via software interrupt calls.

The library code, stored in TCLIB.ASM, looks like,


; TCLIB.ASM, library routine for int86() calls from EMBEDDED CODE
; designed by SE2, 1990
; works with CROM.H
; turboc code must be compiled using 
; tcc -a- -c -f- -G- -K -B -ml -M -N- -O- -r- -v- -y- -Z- -S -O- %1.c


public  _enable
public  _disable
public  _setvect
public  _int86
public  _inportb
public  _outportb

CODE    segment para PUBLIC 'CODE'
	assume  cs:CODE
	name    tclib

; void enable( void ) ;
; enable interrupts
_enable proc    far
	cli
	ret
_enable endp

; void disable( void );
; disable interrupts
_disable        proc    far
	sti
	ret
_disable        endp

; void setvect( int vetcnumber, void (*ptr)( void ) );
; set interrupt vector to function code
_setvect   proc far
	   push bp              ;acess local vars inside functions
	   mov  bp,sp           ;use bp to acess passed parameters
	   push ax
	   push bx
	   push dx
	   push es
	   mov  al, byte ptr [bp+6]  ; int number
	   mov  ah, 0
	   rol  ax, 1
	   rol  ax, 1
	   mov  bx, ax
	   xor  ax, ax
	   mov  es, ax
	   mov  dx, [bp+10]           ; segment
	   mov  es:[bx+2], dx
	   mov  dx, [bp+8]           ; offset
	   mov  es:[bx], dx
	   pop  es
	   pop  dx
	   pop  bx
	   pop  ax
	   pop  bp
	   ret
_setvect   endp

; void int86( int intnumber, union REGS *, union REGS *);
_int86  proc    far
	push    bp              ;acess local vars inside functions
	mov     bp,sp           ;use bp to acess passed parameters
	push    ax
	push    bx
	push    cx
	push    dx
	push    ds
	mov     ax,[bp+10]      ;get segment addr of inregs
	mov     ds,ax
	mov     si,[bp+8]      ;copy .h.. into cpu registers
	mov     ax,[si]         ;ds:si
	mov     bx,[si+2]
	mov     cx,[si+4]
	mov     dx,[si+6]
	mov     di,[si+10]
	mov     si,[si+8]
	cmp     word ptr [bp+6],0010h
	je      short int10
	cmp     word ptr [bp+6],0016h
	je      short int16
	mov     ax,-1
	jmp     short exit

int10:  int     10h
	jmp     short exit

int16:  int     16h
	jmp     short exit

exit:   push    ax
	mov     ax,[bp+14]      ;save regs in program model
	mov     ds,ax
	pop     ax
	mov     si,[bp+12]
	mov     [si],ax
	mov     [si+2],bx
	mov     [si+4],cx
	mov     [si+6],dx
	mov     [si+8],si
	mov     [si+10],di
	jnc     short ex2
	mov     word ptr [si+12],0001h  ; carry flag set
	jmp     short ex3
ex2:    mov     word ptr [si+12],0000h  ; clear carry flag
ex3:    pop     ds                      ;restore registers
	pop     dx
	pop     cx
	pop     bx
	pop     ax
	pop     bp
	ret
_int86  endp

; char inportb( int port );
_inportb        proc    far
	push    bp              ;acess local vars inside functions
	mov     bp,sp           ;use bp to acess passed parameters
	push    dx
	mov     dx,[bp+6]      ; get port address
	xor     ax,ax           ; clear ax
	in      al, dx          ; read from port into al register
	mov     ah, 00h         ; ensure high byte of ax is cleared
	pop     dx
	pop     bp
	ret
_inportb        endp

; void outportb( int port, char value );
_outportb   proc        far
	push    bp              ;acess local vars inside functions
	mov     bp,sp           ;use bp to acess passed parameters
	push    dx
	mov     dx,[bp+6]      ;get port address
	mov     al, byte ptr [bp+8]
	out     dx, al
	pop     dx
	pop     bp
	ret
_outportb       endp

CODE    ends
	end

The C program looks like,

/* test.c */
#include "crom.h"

union REGS regs;

void clear()
{
	regs.h.ah = 15;         /* get video mode */
	int86( 0x10, &regs, &regs );
	regs.h.ah = 0;          /* set video mode */
	int86( 0x10, &regs, &regs );
	regs.x.dx = 0;          /* set cursor */
	regs.h.ah = 2;
	int86( 0x10, &regs, &regs );
}

void writechar( char ch )
{
	regs.h.ah = 14;         /* write tty */
	regs.h.bh = 0;
	regs.h.al = ch;
	int86( 0x10, &regs, &regs );
}

char readkey()
{
	regs.h.ah = 0;
	int86( 0x16, &regs, &regs );
	return regs.h.al;
}

main()
{
	char ch;

	clear();
	for( ; ; )
	{
		ch = readkey();
		writechar( ch );
	}
}

The sequence of commands necessary to generate the .EXE file is,
	tasm /mx tcstart
	tasm /mx tclib
	tcc -a- -c -f- -G- -K -B -ml -M -N- -O- -r- -v- -y- -Z- -S -O- test
	tlink /m tcstart test tclib, test, test

The configuration file TEST.CFG has the following format,
	dup DATA CONST
	class CODE = 0xf600
	class STACK = 0x1000
	class DATA = 0x2000
	order DATA DATAEND BSS BSSEND
	order CODE CODEEND CONST
	rom CODE CONST

Finally, the memory image for storage in EPROM is generated using the LOCATE utility as follows,
	locate test
	hexbin2  test.hex  test.bin  i  f600


GENERATING ROMCODE (IBM-PC/XT, F600:0000) USING INTEL iC86
There are only a few small changes required for doing the same test under the INTEL compiler. The header file CROM.H remains the same, as is the source file TEST.C

The library code, stored in CLIB.ASM, is altered slightly by removing the leading underscores from the function names.

The sequence of commands necessary to generate the EPROM image is,

	asm86           c86f600.asm
	asm86           c86clib.asm
	ic86            test.c LARGE
	link86          c86f600.obj,test.obj,c86clib.obj to test.lnk nobind
	loc86           test.lnk order(classes(stack,data,code)) &
	>> order(segments(STACK,TEST_DATA,CODE,TEST_CODE)) &
	>> ad(sm(stack(10000h),test_data(20000h),(code(0f6000h)))
	oh86    test to test.hex


DESIGNING TURNKEY PC SYSTEMS
A turnkey system automatically executes when power is applied, thus when the PC is turned on it will execute the system software application without user intervention.

Examples of these are

The PC provides a low cost entry into turnkey systems. By adding a memory board and software in EPROM, it can quickly take the place of a custom developed system.

The TWO basic methods are,


RAM DISK/FLOPPY BOOT
This has the advantage of very large programs. Software is easily upgraded by issuing a new disk. It requires little extra hardware or development tools.


MEMORY BOARD
This is needed for high risk systems, and where the software program is relatively small. Such systems do not require a floppy disk, thus are unaffected by smoke and dust, as well as accidental damage.

The PC, upon power-on, executes routines in the ROM BIOS chip. The design of the computer allows the execution of programs stored in external EPROM, examples being those stored on EGA and XT hard disk controller cards.

The memory region C0000 - EFFFF was reserved for this purpose. The ROM BIOS routine, upon power on, checks each 2k block for a special signature byte (AA, 55), and, if found, executes the program stored there. In the case of the EGA card, this would patch the existing int 10h vector into its own code space, then return to the ROM BIOS routine. An embedded system would not return.

Lets look at the change required to implement a turnkey system out of our existing rommable code demonstration program we developed earlier, residing at F600:0000. We shall use the ic86 compiler to do this.

Below are the changes required to the c86start.asm file

; c86d000.asm for Intel Compiler
name        c86d000

extrn           main : far

STACK           segment para stack 'STACK'
		public top
		db      1024 dup ('STACK')
top             label   word
STACK           ends

CODE            segment byte public 'CODE'
public          entry
		assume  cs:CODE, SS:STACK
		db      55h
		db      0aah
		db      40h     ;length of eprom /512
		jmp     entry
start           proc    far
entry:          cli
		mov     ax, STACK
		mov     ss, ax
		mov     ax, offset top
		mov     sp, ax
		sti
		push    ds
		mov     ax, 0040h       ; set warm boot flag
		mov     ds, ax
		mov     si, 0072h
		mov     word ptr [si], 1234h
		pop     ds
		cli
		mov     al, 80h ; enable NMI
		out     0A0h, al
		mov     al, 0bch        ; enable 8259 PIC
		out     21h, al
		sti
		call    main
		jmp     entry
start           endp
CODE            ends
end

NOTE the extra code required to enable the NMI (used by the parity system) and enable the default IRQ's on the 8259 PIC.

We shall use a standard PC Prototype board, which has had a 27256 Eprom socket wire wrapped as a 32k block beginning at 0D0000h. This board cost $120 and only took 30 minutes to wire-wrap and configure for use.

The command sequence to generate the HEX file is,

	asm86           c86d000.asm
	asm86           c86clib.asm
	ic86            test.c LARGE
	link86          c86d000.obj,test.obj,c86clib.obj to test.lnk nobind
	loc86           test.lnk order(classes(stack,data,code)) &
	>> order(segments(STACK,TEST_DATA,CODE,TEST_CODE)) &
	>> ad(sm(stack(10000h),test_data(20000h),code(0d0000h)))
	oh86    test to test.hex

The code is generated then stored into EPROM. The memory board is configured to 0d0000h and installed with the EPROM into the target PC/XT. When the PC is turned on, it executes the self tests of CPU, ROM and RAM in the ROM BIOS before checking for external EPROMS. It will then find ours, and execute the code stored there.


STRUCTURE OF THE PC/XT ROM BIOS ROUTINES
The following information was gleamed by reading the IBM technical reference manual (BIOS listing), as well as running SOURCER on a PC compatible. SOURCER is a ROM disassembler.

In a turnkey system running in external space C8000-F6000, extra code in the startup file is necessary to enable the NMI and the PIC, as well as reset the warm boot flag used by the CTRL_ALT_DEL routine.


PC/XT SOURCE ROUTINES TO INITIALIZE THE MOTHERBOARD
;SETUP 8237 DMA CONTROLLER
	mov     al, 0ffh                ; dmsa count 64k channel 0
	out     1, al           ; lsb
	out     1, al           ; msb
	mov     al, 58h         ; set dma mode ch0 ready, auto int      
	out     0bh, al
	mov     al, 0           ; enable dma
	out     8, al
	out     0Ah, al         ; set chn0 for refresh of RAM


;SETUP 8253 TIMER COUNTER CHIP
	mov     ax, 1254h
	out     43h, al
	xchg    ah, al
	out     41h, al
	mov     cx, 3
	mov     al, 41h
lp2:    out     0bh, al
	inc     al
	loop    lp2


;SETUP 8259 PRIORITY INTERRUPT CONTROLLER
	mov     al, 13h ; ICW1-Edge, Single ICW4
	out     20h, al
	mov     al, 8   ; ICW2 - int type 8-F
	out     21h, al
	inc     al
	out     21h, al ; ICW4 - buffered 8086 mode
	mov     al, 0bch        ; enable default IRQ's
	out     21h, al


A POOR MANS TURNKEY SYSTEM
This involves using floppy disk boot. The source file is compiled to generate an .EXE file which is placed on a formatted DOS diskette. A file called CONFIG.SYS is created, which has the following line added to it,
	SHELL=TEST.EXE
When the PC/XT boots, the file will be run instead of command.com, the standard command-line DOS interface program. The advantages of this system are,

The DOS consists of two files, IO.SYS and MSDOS.SYS, which are loaded when the computer boots from disk. Using standard libraries simplifies design and coding, as well as allowing complicated software to be written (menus, database etc)


© Copyright Brian Brown, 1984-2000. All rights reserved.
menu prev