Memory-mapped mode on a Compact-Flash RAM card



I finally got a working interface between the 68HC912 SBC and a compact flash card. This is how I did it.

COMPACT FLASH CARDS

Compact Flash (CF) cards are produced by several manufacturers to a standard published by the CompactFlash Association. This standard covers both the hardware (connector specification and pinout) and the software (use of the pins) associated with these cards. Several manufacturers (SanDisk, Silicon Systems) publish a spec for these cards. Unfortunately, none of these specs contain any description of how you actually operate the CF, they merely describe the registers and command structures and leave it to you to figure out what combination in what order will do what you want.

I found an application note (from Silicon Storage Technology) describing the use of a CF card with a 8051-compatible single board computer (SBC) that they also offer. The hardware interface and software I came up with resulted from study of both this app note and the CF specs from the companies noted above.

CF cards are often used as static hard drives and must interface with a PC as though they were ATA hard drives, with a File Allocation Table, cylinders, sectors, and heads. In order to permit standard file command structures, each CF card contains a processor to interface the host computer to the card's actual RAM. Note that the disk structure form of usage requires data be stored in discrete chunks -- 512 bytes per sector in this case.

There is also a "Memory Mapped" mode available, wherein one can address the card as a large linear memory. This mode is much simpler to use than the ATA disk mode. It has the same limitation on sector sizes, however. You must read or write 512 byte chunks at a time. This is the mode I chose to use in my applications.

The CF communicates with the host via a set of memory-mapped registers. The memory-mapped mode register set consists of 8 sequential registers:

\CE2

\CE1
\REG
A3
A2
A1
A0
READ
WRITE
1
0
1
0
0
0
0
DATA
DATA
1
0
1
0
0
0
1
ERROR
FEATURE
1
0
1
0
0
1
0
SECTOR CNT
SECTOR CNT
1
0
1
0
0
1
1
SECTOR NUM
SECTOR NUM
1
0
1
0
1
0
0
CYLINDER LO
CYLINDER LO
1
0
1
0
1
0
1
CYLINDER HI
CYLINDER HI
1
0
1
0
1
1
0
DRIVE HEAD
DRIVE HEAD
1
0
1
0
1
1
1
STATUS
COMMAND


These registers are 'addressed' by a combination of address lines (A3-0) and special input signals (\CE1, \CE2, and \REG). The proper combination of these lines will permit reading or writing any of these registers.

Data is read and written through the DATA register -- 512 bytes at at time. Once started, the correct number of bytes must be read or written. The location on the CF of the data is set by the Sector number and the Cylinder (HI-MID-LO) value. One can setup a linear addressing mode (Logical Block Addressing, or LBA) that uses the last 4 bits of the DRIVE HEAD register, the two bytes of the CYLINDER registers, and the SECTOR NUMber as a 28-bit address (LBA27-0). Writing $Ex to DRIVE HEAD register enables the LBA addressing mode, sets the DRIVE # to 0, and sets the addresses LBA27-24 to x.

The SECTOR CouNT tells the CF card how many sectors you plan to read or write. The simplest choice is 1 but some (all?) CF cards have a buffer size that is most efficient for writing data.

One can read/write data as 16-bit words or as 8-bit bytes. Since the 68HC912 SBC has an 8-bit data bus, I have elected to use the 8-bit mode. The FEATURE register (write-only) is used to enable 8-bit transfers by writing it with $01.

The COMMAND register is used to cause the internal CF processor to execute commands. For example, one would write $EF to COMMAND after setting the FEATURES register.

The COMMAND register is also used to tell the CF internal processor that you are about to read or write a sector of data. The last thing before reading or writing is to send $20 (read) or $30 (write) to the COMMAND register.

STATUS is used to determine if the CF is busy (bit 7 set) or if an error has occurred (bit 0 set). This register should be read after every command; wait in a loop until bit 7 is cleared.

Writing a $0C and then a $08 to DEVice ConTRoL register causes a soft reset of the CF card. This may not be necessary every time, but it is a sure way to start with the card in a known condition.

HARDWARE

A 74HC688 8-bit comparator is used to memory-map the CF card to address $7EA0-7EAF. I chose this address (in RAM space) because I use a DS1244 RTC-NVRAM for RAM and the clock registers are in the $7FFx space. This puts all of my memory-mapped stuff in one place in RAM. I copied the memory-mapped peripheral method from New Micros where the upper 8 address bits are compared to a fixed number in a 74HC688 comparator. This lets me select any 256-byte section of memory to use for a memory-mapped peripheral such as the CF card. When any address in this range is addressed, the '688 output goes low. This signal is diode-ORed to \MEMDIS, to signal the CPU that an external device (not RAM) has been selected.

The output of the '688 is also used, with E-clock and R/W, to generate the \READ and \WRITE strobes to the CF card. Reading an address in the $7Exx space generates a \READ to the CF card, writing an address in the $7Exx space generates a \WRITE.

I connected address lines as follows (also see the schematic):

68HC912

Compact Flash
A7
\CE2
A6
\CE1
A5
\REG
GND
A10-A15
A4
A9
GND
A4-A8
A0-A3
A0-A3


With this arrangement, the DATA registers for the CF occur at $7EA0-7EAF and the control pins (\CE1, \CE2, \REG) are set appropriately for the memory-mapped mode.

Address lines A10 and A8-4 on the CF are not used and are grounded. Data lines D7-0 are connected to the CPU data lines.

\RDY can be used to verify the CF is ready to accept commands by sensing it on an ADC channel; the STATUS register can also be used and this is the mode employed in my code.

\CD1 can be used to sense the presence of a CF card. This line goes low when a card is installed in the socket. This flag is provided since CF cards can be hot-swapped in most applications. My application doesn't require this and so I don't bother to sense this line. The schematic shows it connected to the ADC, however.

SOFTWARE:

This is the tricky part, of course. I ended up copying the code from an application note by SST, Inc. that showed how to control a CF card in Memory Mode from an 8051 microprocessor. After considerable study (since I don't know the assembly code mnemonics for this processor), I doped out the following sequence:

1. Initalize the CF card by doing a soft reset.
2. Setup the CF by enabling 8-bit transfers and setting Drive # = 0.

Then, to read (write) to the CF, which must be done in sectors of 512 bytes at a time,

3. Setup the address registers (sector/cylinder/head or LBA address).
4. Send a read (write) command to the command register.
5. Read (write) 512 bytes to (from) a RAM buffer.

This sequence is included in the test code below. This code was built around an application program and thus has some extraneous words; I apologize if these cause confusion. However, this code contains the machine-language routines I wrote to read and write data blocks quickly and efficiently. These might prove useful. The same functions can be accomplished in Forth -- and, in fact, my first operating code was written in Forth. But I like machine code for critical routines.

The sample code does the card initialization. Then a buffer in RAM is filled with a bit pattern and a byte from the buffer displayed. This buffer is written to the card. The buffer is zeroed and then the same sector on the CF card is written into the buffer. Then a byte from the buffer is displayed and the contents compared against the original bit pattern. The sector address is incremented by one and this process repeats 44 more times.

cold

exram ( enable external RAM

hex

1000 dp ! ( load code into external RAM

( FILE TESTCF1.4th -- code to test CF-RAM on TAPS-NG CPU/IO card
( 19 May 2006

( Runs from RAM

( Port use is as follows:
(
( PP0 is RUN/STOP - enables transmissions or noise datafiles
( PP1 is SHTDWN - controls power to CPU card peripherals
( high = off
( PP2 is \ADC2 - MAX111 ADC
( PP3 is \ADC1 - MAX1067 ADC
( PP4 is \DAC - AD5300BRT DAC
( PP5 is FSELECT - to select between F1 and F2 in DDS
( PP6 is \DDS - frames 16-bit transfers to DDS
( PP7 is \XGATE - transmit gate pulse

( PT0 is FREQ1 - Frequency input to timer
( PT1 is FREQ2 - Frequency input to timer
( PT2-4 is MUX - M0-M2 selects acoustic channels
( PT5-7 is GAIN - G0-G2 selects post-IF gains

( PDLC5 is TRANS - controls power to Transmitters
( PDLC6 is INSTS - controls power to External Instruments

( -------------------------- CONSTANTS -------------------------------
hex

006f constant portad ( on-board ADC data register
0056 constant portp ( Port P data register
0057 constant ddrp ( Port P data direction register
00ae constant portt ( Port T data register
00af constant ddrt ( Port T data direction register
00fe constant pdlc ( Port DLC Data register
00ff constant ddrdlc ( Port DLC data direction register

0080 constant tios ( Timer I/O select register
0084 constant tcnt ( Timer counter register
0086 constant tscr ( Timer system control register
008b constant tctl4 ( Timer control register 4
008d constant tmsk2 ( Timer interrupt mask register 2
008e constant tflg1 ( Timer interrupt flag register 1
008f constant tflg2 ( Timer interrupt flag register 2
0090 constant tc0 ( Timer input compare register 1
0091 constant tc1 ( Timer input compare register 2

00d0 constant spocr1 ( spi 1 control register
00d1 constant spocr2 ( spi 2 control register
00d2 constant spobr ( spi baud rate register
00d3 constant sposr ( spi status register
00d5 constant spodr ( spi data register

00d6 constant ports ( Port S data register
00d7 constant ddrs ( Port S data direction register

( Addresses decoded to R/W to CF: $7EA0 - 7EAF
7ea0 constant cfdata ( byte-port to flash
7ea1 constant cferror ( read-only
7ea1 constant features ( write only
7ea2 constant sctrcnt ( sector count register
7ea3 constant LBA0 ( LBA0:7 = Sector Number
7ea4 constant LBA1 ( LBA8:15 = Cylinder low
7ea5 constant LBA2 ( LBA16:23 = Cylinder high
7ea6 constant LBA3 ( LBA24:27 = head count
7ea7 constant status ( read only
7ea7 constant command ( write only
7eae constant dev-ctrl ( write only
7e90 constant config ( r/w config register
7e96 constant socket ( r/w socket & copy register

-1 constant true
0 constant false

( ------------------------- VARIABLES -------------------------------

variable errorcount ( holds # of errors in compare
variable testbyte ( holds byte to write to CF
2variable fadr ( long address of sector in FLASH
2variable tfadr ( hold starting flash address

( REGISTER + 0 = errors
( REGISTER + 1 = sector count
( REGISTER + 2 = sector # or LBA 0:7
( REGISTER + 3 = cylinder low or LBA 8:15
( REGISTER + 4 = cylinder hi or LBA 16:23
( REGISTER + 5 = head count or LBA 24:27
( REGISTER + 6 = status

create registers ( place to download CF reg's
7 allot

create buff
100 allot ( Text Input Buffer

create buffer
512 allot ( compact flash sector buffer

hex
( --------------------- COMPACT FLASH RAM ROUTINES --------------------------


code-sub write-cf
3b c, ( pshd ; save D accumulator
34 c, ( pshx ; save X register
35 c, ( pshy

( ; Setup Sector Address values

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
8601 , ( ldaa #01
7a c, sctrcnt , ( staa sctrcnt

cd c, FADR , ( ldy #FADR ; point Y at FLASH address
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a643 , ( ldaa 3,y ; LB of address
7a c, lba0 , ( staa lba0

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a642 , ( ldaa 2,y ; ML byte of address
7a c, lba1 , ( staa lba1

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a641 , ( ldaa 1,y ; set Low byte of ADR
7a c, lba2 , ( staa lba2

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a640 , ( ldaa 0,y ; set High byte of ADR
840f , ( anda #$0f ; only use low 4 bits
8ae0 , ( ora #$e0 ; set CF1, LBA enabled
7a c, lba3 , ( staa lba3

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
8630 , ( ldaa #write
7a c, command , ( staa command ; set mode to WRITE

( Write a block of 512 bytes into FLASH, checking RDY=1 before each write

ce c, buffer , ( ldx #buffer ; X points at BUFFER
cd c, cfdata , ( ldy #cfdata ; Y points at DATA port to FLASH
c600 , ( ldb #256 ; B counts words
( wordmove:
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
180a , 3070 , ( movb 1,x+,1,y+ ; move a byte, post-increment
03 c, ( dey ; adjust Y back to DATA
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
180a , 3070 , ( movb 1,x+,1,y+ ; move a byte, post-increment
03 c, ( dey
04 c, 31eb , ( dbne b,wordmove ; continue till all are moved

31 c, ( puly
30 c, ( pulx ; recover registers
3a c, ( puld ; and accumulator
3d c, ( rts
end-code

code-sub read-cf
3b c, ( pshd ; save D accumulator
34 c, ( pshx ; save X register
35 c, ( pshy

( ; Setup Sector Address values

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
8601 , ( ldaa #01
7a c, sctrcnt , ( staa sctrcnt

cd c, FADR , ( ldy #FADR ; point Y at FLASH address
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a643 , ( ldaa 3,y ; LB of address
7a c, lba0 , ( staa lba0

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a642 , ( ldaa 2,y ; ML byte of address
7a c, lba1 , ( staa lba1

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a641 , ( ldaa 1,y ; set Low byte of ADR
7a c, lba2 , ( staa lba2

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
a640 , ( ldaa 0,y ; set High byte of ADR
840f , ( anda #$0f ; only use low 4 bits
8ae0 , ( ora #$e0 ; set CF1, LBA enabled
7a c, lba3 , ( staa lba3 ; set high byte of ADR

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
8620 , ( ldaa #read
7a c, command , ( staa command ; set mode to READ

( Read a block of 512 bytes into RAM, checking RDY=1 before each write

cd c, buffer , ( ldy #buffer ; X points at BUFFER
c600 , ( ldb #256 ; B counts words
( wordmove:
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
b6 c, cfdata , ( ldaa cfdata ; read a byte
6a40 , ( staa 0,y ; save to BUFFER
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
b6 c, cfdata , ( ldaa cfdata
6a41 , ( staa 1,y
0202 , ( iny 2X
04 c, 31e9 , ( dbne b,wordmove ; continue till all are moved

31 c, ( puly
30 c, ( pulx ; recover registers
3a c, ( puld ; and accumulator
3d c, ( rts
end-code

( Read the ID sector on the compact flash card

code-sub read-cf-id
3b c, ( pshd ; save D accumulator
34 c, ( pshx ; save X register
35 c, ( pshy

( Send IDENTITY code

4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
86ec , ( ldaa #identify
7a c, command , ( staa command ; set mode to READ

( Read a block of 512 bytes into RAM, checking RDY=1 before each write

cd c, buffer , ( ldy #buffer ; X points at BUFFER
c600 , ( ldb #256 ; B counts words
( wordmove:
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
b6 c, cfdata , ( ldaa cfdata ; read a byte
6a41 , ( staa 1,y ; save to BUFFER
4f6f , 01fc , ( brclr portad,#$01,* ; ensure RDY is high
b6 c, cfdata , ( ldaa cfdata
6a40 , ( staa 0,y ; note little-endian data format
0202 , ( iny 2X
04 c, 31e9 , ( dbne b,wordmove ; continue till all are moved

31 c, ( puly
30 c, ( pulx ; recover registers
3a c, ( puld ; and accumulator
3d c, ( rts
end-code
( ---------------------- SPI INIT SUBROUTINE -----------------------------

code-sub spi-init ( code to setup spi i/o
4cd6 , 80 c, ( bset ports,#$80 ; set SS line high
180b , e0 c, ddrs , ( movb #$e0,ddrs ; Configure PORT S ddr
180b , 02 c, spobr , ( movb #$02,spobr ; set SCLK rate = 1 MHz
180b , 12 c, spocr1 , ( movb #$12,spocr1 ; MSTR=1, CPOL=CPHA=0
180b , 08 c, spocr2 , ( movb #$08,spocr2 ; SPI 2 outputs, active pullups
96d3 , ( ldaa sposr ; 1st step to clear SPIF flag
96d5 , ( ldaa spodr ; 2nd step to clear SPIF flag
4cd0 , 40 c, ( bset spocr1,#$40 ; enable spi
3d c, ( rts
end-code

code-sub spi-off
4dd0 , 40 c, ( bclr spocr1,#$40 ; disable spi
3d c,
end-code

( ---------------------- UTILITY ROUTINES ---------------------------------

code-sub power-on
4cfe , 20 c, ( bset pdlc,#$20 ; enable board power
3d c, ( rts
end-code

code-sub power-off
4dfe , 20 c, ( bclr pdlc,#$20 ; disable board power
3d c, ( rts
end-code

code-sub insts-on
4cfe , 40 c, ( bset pdlc,#$40 ; enable insts power
3d c, ( rts
end-code

code-sub insts-off
4dfe , 40 c, ( bclr pdlc,#$40 ; disable insts power
3d c, ( rts
end-code

code-sub cpu-off
180b , 0a c, portp , ( movb #$0a,portp ; power off, bits = 0
3d c, ( rts
end-code

code-sub cpu-on
180b , fd c, portp , ( movb #$fd,portp ; power on, bits = 1
3d c, ( rts
end-code

( --------------------- FORTH CODE ----------------------------------

: bs
20 0 do 08 emit loop
;
: tic-init
fc tios c! ( Timer bits 0-1 as input capture
33 tmsk2 c! ( set timer to 1 uS ticks
80 tscr c! ( enable timer
;
decimal
: wait-on
20000 0 do
3 0 do I drop loop
loop
;
hex
: busy? ( - )
begin
status c@ ( read status register
80 and ( mask bit 7
0= ( is it clear?
until
;
: get-regs ( read CF registers
busy?
cferror c@ registers c!
sctrcnt c@ registers 1+ c!
LBA3 c@ registers 2 + c!
LBA2 c@ registers 3 + c!
LBA1 c@ registers 4 + c!
LBA0 c@ registers 5 + c!
status c@ registers 6 + c!
;
: disp-regs
hex
." error code = " cferror c@ . cr
." sector count = " sctrcnt c@ . cr
." Hi address = " LBA3 c@ . cr
." Mid address = " LBA2 c@ . cr
." Mid address = " LBA1 c@ . cr
." lo address = " LBA0 c@ . cr
." status = " status c@ . cr decimal
;
hex
: setup-CF
cr ." Setting up CF card ... "
busy?
01 features c! ( enable 8-bit data transfer
busy?
fadr c@ LBA0 c! ( install current sector address
busy?
fadr 1+ c@ LBA1 c!
busy?
fadr 2 + c@ LBA2 c!
busy?
fadr 3 + c@ 0f and
e0 or LBA3 c! ( Drive 0, LBA addressing
busy?
ef command c! ( execute commands just loaded
busy?
." done" cr
;
decimal
: disp-status
cr ." BAE SYSTEMS " cr
." TAPS New Generation " cr
." CF-RAM TEST CODE v1.0" cr cr
;
( ----------------------- TEST ROUTINES ---------------------------------
decimal
: fill-buff ( N - )
512 0 do dup buffer i + c! loop
drop
;
: show-buff
buffer 40 dump
;
: compare ( n - flg)
0 errorcount !
512 0 do
dup buffer I + c@
= not if
1 errorcount +!
then
loop drop
errorcount @ 0= ( set flg=TRUE if no errors
;
: write45
cr hex
45 0 do ( write 45 sectors of data
testbyte c@ fill-buff ( fill buffer with byte pattern
buffer 18 + c@ .
write-cf ( write a sector to CF card
fadr 2@ 1. d+ fadr 2! ( increment sector address
loop
cr decimal
;
: read45
cr hex
45 0 do ( read 45 sectors of data
0 fill-buff ( clear buffer
read-cf ( read a sector from CF card
buffer 37 + c@ .
testbyte c@ compare
drop
( if 46 emit else 42 emit then
fadr 2@ 1. d+ fadr 2! ( increment sector address
loop
cr decimal
;
: ud. ( d - ) ( print unsigned double word
<# #s #> type
;
: cfram-test ( n - n )
cr ." Testing Compact Flash RAM card " cr
decimal
read-cf-id 4 spaces
buffer 2 + @ dup . ." cylinders " cr 4 spaces
buffer 6 + @ dup . ." heads " cr 4 spaces
*
buffer 10 + @ dup . ." bytes/sector " cr 4 spaces
buffer 12 + @ dup . ." sectors/track " cr 4 spaces
* um* d. ." bytes available " cr 4 spaces
buffer 42 + @ . ." sectors/buffer " cr cr

get-regs
disp-regs

fadr 2@ tfadr 2! ( save in temp loc'n
setup-cf
." Starting sector address for write = " fadr 2@ d.
write45
." Ending sector address = " fadr 2@ d. cr
tfadr 2@ fadr 2! ( put starting LBA in FADR
setup-cf
." Starting sector address for read = " fadr 2@ d.
read45
." Ending sector address = " fadr 2@ d. cr
." Press any key to continue " key drop cr
;
: do-choice ( n - flag )
dup 1 = if cfram-test then
0 = if true else false then
;
: get-choice ( - n )
cr ." Choice = " key dup emit
48 -
;
: disp-menu
." TEST MENU" cr
." testcf1.4th" cr
." 0 END" cr
." 1 CF RAM test" cr cr
;
hex
: init
exram ( hopefully redundant
001a 00c0 ! ( set baud rate to 19200
0a portp c! ( set ADC1 high, CPU peripherals off
00 pdlc c! ( set external power off
00 portt c! ( set MUX to CH 0, GAIN to 0
ff ddrp c! ( set Port P DDR
ff ddrdlc c! ( set Port DLC DDR
fc ddrt c! ( set Port T DDR; bits 0-1 are inputs
fc tios c! ( set Port T TIOS same
86 c@ 80 and 86 c! ( set TEN bit of TSCR, start timer
01 80 c! ( set output compare Timer 1
power-off ( disable ext power
cpu-off ( disable cpu peripherals
0c dev-ctrl c! ( soft reset of CF
08 dev-ctrl c!
30000 0 do I drop loop ( short delay
get-regs
setup-CF
2 places
decimal
;
decimal
: main
init spi-init spi-off cr disp-status cr
power-on cpu-on wait-on
0002. fadr 2! ( put starting LBA in FADR
139 testbyte c!
setup-cf
begin
disp-menu
get-choice
do-choice
until cr
power-off cpu-off
;

( TESTCF1.4th - TAPS NG CPU TEST CODE - 19 May 2006

here u. cr
decimal

APPLICATIONS:

My first use of the CF-RAM card was to store large quantities of data from a multi-frequency acoustic sensor. A buffer was established in RAM, filled with echo data, and transferred to CF-RAM. A simple buffer counter was all that was needed to keep track of buffer addresses. (Note: I invariably use a Dallas Semiconductor 32kB NV-RAM in the RAM socket. This allows me to keep counters and such in RAM even when power is removed. In most sensors, I use the Real Time Clock version of the NV-RAM; The choice of memory-mapping done in the next section is affected by the presence of the RTC registers at the top of RAM memory.

Somewhat later, I realized that I could also store programs on the CF-RAM card and load them into RAM for execution as needed. Whether this works for you or not depends on the relative amount of code you have versus the size of Flash memory and the amount of data space you need. It turned out that I had way more code than could easily fit into Flash memory on the CPU chip but, with judicious use of the CF-RAM for data storage, there was ample data space available. So I broke my complex operating program up into functional chunks (the several operating modes, plus test modes) that could be loaded and executed as needed.

One problem you face when powering up such a system is -- what program is resident? Is it a valid program? I ended up saving some key data at fixed RAM memory locations that could be tested. For example, I do a byte-wise checksum over a fixed chunk of memory (one that should contain valid code for all the programs) and compare it to a value stored in RAM. If these agree, and if a mode value also stored in RAM is one of the valid modes, then I read an execution address from RAM and EXECUTE it (thanks to Chris at NMI for this suggestion).

Each program that I write must conform to the RAM layout of all the other programs. My programs share a data structure that I keep in RAM right after the execution address, mode value, and checksum. I put these values starting at $1000 and load my data structure and code beginning at $1010.

To make things simpler (I love simple), I store every program on CF-RAM starting at an even boundary: $100, $200, $300, etc. I also read in a fixed number of sectors every time, even though the programs vary in length. After the program is loaded by the driver program in Flash memory, the byte checksum is calculated and stored in RAM. A variable in RAM is also set TRUE to indicate that the program has just been loaded and not yet executed. I use this to know if the user has to enter some setup data for this new mode. You might not need this.

The programs I write end with this FORTH code:

' main cfa prgm-adrs ! ( put execution adr at $1000
true first-flag ! ( set flag for first load of program

where MAIN is the name of the primary word in my program, PRGM-ADRS = $1000, and FIRST-FLAG = $1004 in my code.

After loading the program to RAM, I load and execute another short program I call SAVEPRGM.4th. This code, listed below, computes a checksum over the specified area of code, saves it, and then saves a fixed number ($38 or 56 decimal) of sectors to CF-FLASH. Note that this code KNOWS that I use routines called INIT, WRITE-PROGRAM-SECTOR and INC-SECTOR in all the programs and so can just call them. Note also that I have specified the starting sector for the code explicitly (I'm also lazy) in the code; in this example, this program is loaded starting at sector $200.

( Compute checksum over program space and store program to CF
variable checksum
variable linelength
hex
: byte-checksum ( - sum )
0 checksum !
4b00 3b00 do
i @ checksum +!
loop checksum @
;
: save-program ( startsector - )
0 fadr 2! ( pointer into CF sectors
byte-checksum 1002 !
0 linelength !
cr ." Saving program to CF ... " cr
38 0 do
fadr 2@ d.
I 200 * 1000 + dup . 4 spaces
write-program-sector
inc-sector
linelength 1+! linelength @ 5 =
if cr 0 linelength ! then
loop cr ." done" cr
;
init
hex

SCHEMATIC:

The schematic on the next page shows the interface I used to drive a compact-flash RAM card as a memory-mapped peripheral using the code, TESTCF1.4th, above. J12 is the I/O connector on both the 68HC11 and 68HC12 cards from New Micros Inc. The code shown is for a 68HC12 card but there is no reason that a 68HC11 CPU card can't drive a CF-flash card.

Separate /READ and /WRITE lines are de-coded from R\W and the E clock lines and NAND'ed with the output of the comparator. Thus a simple read or write operation, within the address limits imposed by the comparator, can be used to send data to and from the CF-flash card.

CF INTERFACE SCHEMATIC