PIC data logger to CF Card file; CF Card filesystem updated; Data readable via Windows PC software.
PIC 18F452 to CF Card to Windows PC Instrumentation Data Collection
Required Environment:
The program is setup to write an instrumentation file of maximum size based on code logic documented in routine 'cf_file_write'. Maximum file size, as the code is implemented, is 32MB. This is because I have only used 16-bit variables to count CF card file segments. 2**16 * 512 = 32MB. Larger file sizes can be generated by modifying the code to use 24-bit variables. A 24-bit variable would accommodate the sector count for the max file size of 4GB.
Instrumentation data is assigned to file: 'PIC_DATA.DAT'. This can be easily changed as it is specified in ROM via a DB string.
Only the file size and first cluster are updated in the directory entry for the file at completion of data collection.
To setup the CF card for use; I format the CF card (FAT16) and then using an edit program save an empty file to "pic_data.dat". The format ensures no data clusters have been allocated and that my file entry will be in the first segment of the root directory. Then its ready to go.
Miscellaneous notes regarding the program:
;****************************************************************************** ;* CF_DB_Inst.ASM ;* ;* Revision: 2 Comments somewhat cleaned-up and working code. ;* Tested file size to 16MB. ;* ;* 03/09/2008 ;****************************************************************************** ;* ;* This program interfaces with a pre-formatted (FAT16) compact flash memory ;* card in PC Card Memory Mode. The disk partition table is read to get the ;* first partition segment offset. Next the BIOS Parameter Block in the ;* boot sector is read to verify the CF card filesystem format is FAT16. ;* The 1st segment of the root directory of the FAT (File Allocation Table) ;* file system is scanned for a specific file (PIC_DATA.DAT). If that file ;* is found, the file is then verified to be empty, zero length, and no cluster ;* number. Instrumentation data is collected in the CF card user data area ;* and linked to the file by mapping the data segments of the file written ;* to the CF card via the cluster chain mapping; which is written to both ;* copies of the CF Card FAT (FAT1 & FAT2). The directory entry of the file ;* is then updated with the starting cluster chain entry and the filesize ;* in bytes. The instrumentation file written to the CF Card is readable ;* on a Windows PC for further data reduction. ;* ;* Program performs 8-bit I/O with CF card. ;* ;* The PIC is clocked via a 4.9152MHz oscillator in my test setup. ;* ;****************************************************************************** list P=PIC18F452 include "p18f452.inc" errorlevel -302 ; suppress message 302 from list file ;****************************************************************************** ;Configuration bits CONFIG OSC = XT CONFIG OSCS = OFF CONFIG PWRT = ON CONFIG BOR = OFF CONFIG WDT = OFF CONFIG CCP2MUX = OFF CONFIG STVR = OFF CONFIG LVP = OFF CONFIG DEBUG = ON CONFIG CP0 = OFF CONFIG CP1 = OFF CONFIG CP2 = OFF CONFIG CP3 = OFF CONFIG CPB = OFF CONFIG WRT0 = OFF CONFIG WRT1 = OFF CONFIG WRT2 = OFF CONFIG WRT3 = OFF CONFIG WRTB = OFF CONFIG WRTC = OFF CONFIG WRTD = OFF CONFIG EBTR0 = OFF CONFIG EBTR1 = OFF CONFIG EBTR2 = OFF CONFIG EBTR3 = OFF CONFIG EBTRB = OFF ;****************************************************************************** ;***** Define substitution text ; #define W 0 ; already in "p18f452.inc" #define F 1 ;****************************************************************************** ;***** Define symbol constants constant EOT=0xFF ; End Of Table ;****************************************************************************** ;***** Define Assembler constants ; ; Banking values for Bank Select Register (BSR) ; constant GPR0=0x00 ; These constants correspond to the constant GPR1=0x01 ; DATABANK names in the linker script constant GPR2=0x02 ; for the BANKED RAM locations. Use constant GPR3=0x03 ; these constants to load the BSR constant GPR4=0x04 ; (Bank Select Register) for variable constant GPR5=0x05 ; access to those assigned to each bank. ; **WARNING** - match these constants to linker script assignments. constant PROG_GPR=GPR0 ; udata prog_vars assigned to gpr0 constant CFOPS_GPR=GPR3 ; udata cfops_vars assigned to gpr3 constant MATH_GPR=GPR3 ; udata math_vars assigned to gpr3 ; ; CF Card command values, 8 bits ; WR_SEC_CMD EQU 0x30 RD_SEC_CMD EQU 0x20 ; ; CF Card port equates ; CF_DATA_IN EQU PORTD CF_DATA_OUT EQU LATD CF_ADDR EQU LATE CF_CONTROL EQU LATC ; output MSB:LSB [LED,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] CF_STATUS EQU PORTC ; input MSB:LSB [LED,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] ; ; input/output out in in out in out out out ; ; CF Card control port (PortC) pin equates ; CE1 EQU 0 OE EQU 1 WE EQU 2 RDY EQU 3 RSET EQU 4 ; reserved here but not implemented in code CD1 EQU 5 WAIT EQU 6 LED EQU 7 ; ; CF Card register addresses - Memory Mapped Addressing ; low 3 bits as follows: (X,X,X,X,X,A2,A1,A0) ; DATA_REG EQU 0x00 ; address of data register ERROR_REG EQU 0x01 ; address of error register FEATUR_REG EQU 0x01 ; address of features register SEC_CNT_REG EQU 0x02 ; address of sector count register SEC_NUM_REG EQU 0x03 ; address of sector number register CYL_LO_REG EQU 0x04 ; address of low cylinder register CYL_HI_REG EQU 0x05 ; address of high cylinder register HEAD_REG EQU 0x06 ; address of head/drive register STATUS_REG EQU 0x07 ; address of status register COMMAND_REG EQU 0x07 ; address of command register ; ; CF program operation error codes ; NO_CARD EQU 0x01 ; CF card not present BAD_FORMAT EQU 0x02 ; CF card file system not FAT16 FIL_NOT_FND EQU 0x03 ; file not found in root dir FILE_IN_USE EQU 0x04 ; file already in use FILE_FULL EQU 0x05 ; maximum file size reached USER_STOP EQU 0x06 ; user requested stop ;****************************************************************************** ;***** Variable definitions ;****************************************************************************** ; Key to naming of multiple byte variables: ; 4-byte variable; ll = low word,low byte ; lh = low word, high byte ; hl = high word, low byte ; hh = high word,high byte ; 2-byte variable; lb = low byte ; hb = high byte acs_vars udata_acs ; interrupt and BSR independent variables ; variables used for context saving w_temp res 1 ; variables used for context saving status_temp res 1 pclath_temp res 1 ; variables used for instrumentation process inst_data_b res 1 ; data byte to write to CF Card data file inst_flag res 1 ; instrumentation interrupt flag end_inst_fl res 1 ; user signal to terminate instrumentation timeout_cnt res 1 ; timeout counter, inst not done in time bank_4_full res 1 ; GPR bank 4 full/empty flag; 0=E, 1=F bank_5_full res 1 ; GPR bank 5 full/empty flag; 0=E, 1=F bnk_4_5_prt res 1 ; active GPR bank pointer; 0x04 or 0x05 inst_bytcnt res 1 ; counter for inst bytes written to GPR banks prog_vars udata ; program variable data ; ; None so far cfops_vars udata ; CF Card routine's variable data ; portC_sh res 1 ; PORTA shadow register for RMW proofing portE_sh res 1 ; PORTE shadow register for RMW proofing cf_io_data res 1 ; CF I/O data byte cf_io_reg res 1 ; CF I/O register address read_ptr_hi res 1 ; 16-bit pointer (cnt) of bytes read read_ptr_lo res 1 read_tgt_hi res 1 ; 16-bit target read_tgt_lo res 1 bytecnt res 1 ; working variable loop_cntr res 1 ; working variable error_code res 1 ; error code for CF routines, reason for stop t_16bit_num: ; temp 16-bit variable for 8-bit numbers t_16bit_lb res 1 ; needed in 16-bit math routines t_16bit_hb res 1 rdf_off_lb res 1 ; offset of 'filename' entry in root dir rdf_off_hb res 1 ; includes 0x400 for bank 4 start address rtdirsiz_lb res 1 ; root directory size in sectors(lb:hb) rtdirsiz_hb res 1 frdirsec_lb res 1 ; first sector of root directory in FAT1 frdirsec_hb res 1 ffat1sec_lb res 1 ; first sector of FAT1 ffat1sec_hb res 1 ffat2sec_lb res 1 ; first sector of FAT2 ffat2sec_hb res 1 fdatasec_lb res 1 ; first data sector in CF Card partition fdatasec_hb res 1 fcluscnt_lb res 1 ; count of clusters needed for file fcluscnt_hb res 1 fatcloff_lb res 1 ; cluster chain link offset into FAT sector fatcloff_hb res 1 filclus_lb res 1 ; cluster number for file data filclus_hb res 1 clus_swc_lb res 1 ; cluster entries written in sector clus_swc_hb res 1 fil_size_ll res 1 ; 4-byte; filesize fil_size_lh res 1 fil_size_hl res 1 fil_size_hh res 1 fil_swrt_lb res 1 ; 2-byte; file sector swritten cnt fil_swrt_hb res 1 fil_dsec_lb res 1 ; 2-byte; writing this data sector fil_dsec_hb res 1 fil_sbwc_lb res 1 ; 2-byte; bytes written in current sector fil_sbwc_hb res 1 ;cf_rw_structure ; Note: keep the following six variables in order cf_rw_scnt res 1 ; sector cnt to read/write; always = 1 cf_rw_snum res 1 ; sector number; LBA 7-0 cf_rw_cyllo res 1 ; sector number: LBA 15-8 cf_rw_cylhi res 1 ; sector number: LBA 23-16; always 0 cf_rw_head res 1 ; always 0xE0 cf_rw_cmd res 1 ; read/write command value ;end_rw_structure cf_rw_reg res 1 ; register offset for read/write sector cmd ; This directory structure left for documentation purposes only ;dir_entry_struct: ; defines a FAT directory entry ;dir_name res 11 ; short name ;dir_attr res 1 ; file attributes ;dir_NTres res 1 ; reserved for Windows NT ;dir_time_10 res 1 ; creation time, millisecond stamp ;dir_time_cr res 2 ; time file created ;dir_date_cr res 2 ; date file created ;dir_lstacc res 2 ; date last accessed ;dir_clus_hi res 2 ; high word of first cluster, 0 for FAT16 ;dir_time_wr res 2 ; time of last write ;dir_date_wr res 2 ; date of last write ;dir_clus res 2 ; 16-bit: 1st cluster of file ;dir_fsize res 4 ; 32-bit: filesize ; ************************************** ; start: CF Partition Table data ; ************************************** pt_vars udata ; variable data corresponding to pt_parm_tbl ; ; MUST CORRESPOND with pt_parm_tbl below pt_parms: pt_active res 1 ; 0x80 if active (bootable), otherwise 0x00 pt_strt_chs res 3 ; start of partition in CHS addressing pt_type res 1 ; partition type: 4, 6, 14 (all FAT16 types) pt_end_chs res 3 ; end of partition in CHS addressing pt_ofset_ll res 1 ; 4-byte number: partition offset (physical) pt_ofset_lh res 1 pt_ofset_hl res 1 pt_ofset_hh res 1 pt_size_ll res 1 ; 4-byte number: partition size in sectors pt_size_lh res 1 pt_size_hl res 1 pt_size_hh res 1 pt_prm_tbl code ; ; MUST CORRESPOND with pt_vars above ; ; Note: byte offset must be in ascending order ; byte number ; offset bytes description ; ---------- ------ ----------- pt_parm_tbl db 0x01, 0xBE, 0x00,0x01 ; PT; Partition active db 0x01, 0xBF, 0x00,0x03 ; PT; Start sector in CHS addressing db 0x01, 0xC2, 0x00,0x01 ; PT; Type db 0x01, 0xC3, 0x00,0x03 ; PT; End sector in CHS addressing db 0x01, 0xC6, 0x00,0x04 ; PT; Relative offset in sectors (LBA) db 0x01, 0xCA, 0x00,0x04 ; PT; Size in sectors db EOT ; end of table marker ; ************************************ ; end: CF Partition Table data ; ************************************ ; ***************************************************** ; start: Boot Sector & BIOS Parameter Block data ; ***************************************************** bs_bio_vars udata ; variable data corresponding to bs_parm_tbl ; ; MUST CORRESPOND with bs_parm_tbl below bs_bio_parms: bpb_bps_lb res 1 ; 2-byte number: bytes per sector bpb_bps_hb res 1 bpb_spc res 1 ; sectors per cluster bpb_res_lb res 1 ; 2-byte number: reserved sector count bpb_res_hb res 1 bpb_num_fat res 1 ; number of FATs bpb_rent_lb res 1 ; 2-byte number: root directory entries bpb_rent_hb res 1 bpb_fsiz_lb res 1 ; 2-byte number: sectors per FAT bpb_fsiz_hb res 1 bpb_hids_ll res 1 ; 4-byte number: hidden sector count bpb_hids_lh res 1 bpb_hids_hl res 1 bpb_hids_hh res 1 bpb_tsec_ll res 1 ; 4-byte number: total sectors in volume bpb_tsec_lh res 1 bpb_tsec_hl res 1 bpb_tsec_hh res 1 bpb_ext_sig res 1 ; signature: should be 0x29 bpb_fil_typ res 8 ; file system type: should be FAT16 bpb_sig_510 res 1 ; signature: should be 0x55 bpb_sig_511 res 1 ; signature: should be 0xAA bs_prm_tbl code ; ; MUST CORRESPOND with bs_bio_parms above ; ; Note: byte offset must be in ascending order ; byte number ; offset bytes description ; ---------- ------ ----------- bs_parm_tbl db 0x00, 0x0B, 0x00,0x02 ; BPB; Count of bytes per sector db 0x00, 0x0D, 0x00,0x01 ; BPB; Sectors per cluster db 0x00, 0x0E, 0x00,0x02 ; BPB; Number of reserved sectors db 0x00, 0x10, 0x00,0x01 ; BPB; Number of FATs db 0x00, 0x11, 0x00,0x02 ; BPB; Cnt of 32-byte entries in root dir db 0x00, 0x16, 0x00,0x02 ; BPB; FAT size in sectors db 0x00, 0x1C, 0x00,0x04 ; BPB; Hidden sectors db 0x00, 0x20, 0x00,0x04 ; BPB; Total sector count db 0x00, 0x26, 0x00,0x01 ; BPB; Extended Boot Signature db 0x00, 0x36, 0x00,0x08 ; BPB; File system type (FAT16) db 0x01, 0xFE, 0x00,0x01 ; BPB; Boot Signature = 0x55 db 0x01, 0xFF, 0x00,0x01 ; BPB; Boot Signature = 0xAA db EOT ; end of table marker ; *************************************************** ; end: Boot Sector & BIOS Parameter Block data ; *************************************************** math_vars udata ; math routine variables ; math_temp res 1 ; working variable bsr_temp res 1 ; save/restore BSR register ; mult16x16U variables a1 res 1 ; 16-bit Multiplicand L:H a2 res 1 b1 res 1 ; 16-bit Multiplier L:H b2 res 1 prod_lwlb res 1 ; 32-bit Product - low word:low byte prod_lwhb res 1 prod_hwlb res 1 prod_hwhb res 1 ; 32-bit Product - high word:high byte ; div16x16U variables accAlo res 1 ; 16-bit Denominator L:H accAhi res 1 quotient_lb: accBlo res 1 ; 16-bit Numerator (also Quotient) L:H quotient_hb: accBhi res 1 remain_lb: accClo res 1 ; 16-bit Remainder L:H remain_hb: accChi res 1 accDlo res 1 ; 16-bit working registers L:H accDhi res 1 ; sub16U variables diff_lb: sub_dst_lb res 1 diff_hb: sub_dst_hb res 1 sub_src_lb res 1 sub_src_hb res 1 ; add16U variables sum_lb: add_dst_lb res 1 sum_hb: add_dst_hb res 1 add_src_lb res 1 add_src_hb res 1 pgm_strings code ; constant strings used in program ; fat16_str db "FAT16" ; const str for required FAT type end_fatstr: file_name db "PIC_DATADAT" ; const str for filename, BPB does not store '.' end_filename: ; between filename 'PIC_DATA' and extension 'DAT' constant FATSTRLEN = end_fatstr - fat16_str - 1 ; -1 for pad byte constant FILSTRLEN = end_filename - file_name - 1 ; -1 for pad byte ; the strings above are odd num of char, so ; assembler adds a pad byte to make a full word ;****************************************************************************** ;***** Interrupt Routines ;****************************************************************************** code 0x0000 ; Start at the reset vector. org 0x000 ; Reset interrupt vector clrf INTCON,ACCESS ; Disable all interrupts goto sys_init ;****************************************************************************** ; High priority interrupt vector. org 0x008 nop ; not currently using high priority ints high_isr_end: retfie FAST ; return from interrupt, enable high ints ; shadow registers used ;****************************************************************************** ; Low priority interrupt vector. org 0x018 movwf w_temp,ACCESS ; save current W register contents movf STATUS,W,ACCESS ; move STATUS to w clrf STATUS,ACCESS movwf status_temp,ACCESS ; save contents of STATUS register movff PCLATH,pclath_temp ; save current copy of PCLATH clrf PCLATH,ACCESS ; reset PCLATH to page 0 ; Check if Timer1 interrupt - used to initiate instrumentation activity. btfss PIR1,TMR1IF,ACCESS goto check_int1 ; No, so check next source bcf PIR1,TMR1IF,ACCESS ; clear interrupt flag ; Reset Timer1 count preset. clrf TMR1L,ACCESS ; prevents inc of high byte during init movlw 0xFC ; FFFF-FCFF=0300=768 cnts movwf TMR1H,ACCESS ; so, int every 768 instructions movlw 0xFF movwf TMR1L,ACCESS ; Set instrumentation data available flag. bsf inst_flag,0,ACCESS ; set instrument available flag goto low_isr_end check_int1: ; Check if Int1 interrupt - used to terminate instrumentation activity. ; Make same priority as interrupt used to write instrumentation so that ; activity completes for the pending inst value before stopping activity. btfss INTCON3,INT1IF,ACCESS goto low_isr_end ; No, so exit bcf INTCON3,INT1IF,ACCESS ; clear the interrupt flag bcf INTCON3,INT1IE,ACCESS ; disable further INT1 interrupts ; Disable source of instrumentation interrupts. bcf PIE1,TMR1IE,ACCESS ; disable Timer1 interrupts bsf end_inst_fl,0,ACCESS ; set end inst flag goto low_isr_end low_isr_end: clrf STATUS,ACCESS ; ensure file register bank set to 0 movff pclath_temp,PCLATH ; restore pre-isr PCLATH register movff status_temp,STATUS ; restore pre-isr STATUS register swapf w_temp,F,ACCESS ; swap w_temp nibbles and return to w_temp swapf w_temp,W,ACCESS ; swap w_temp into W retfie 0 ; return from interrupt, enable low ints ; shadow registers not used ;****************************************************************************** ;***** Initialization Routine ;****************************************************************************** sys_init: ; Interrupt initialization. bcf INTCON,GIEH,ACCESS ; disable all interrupts ; PORTC/TRISC initialization - CF Card control lines. clrf PORTC,ACCESS clrf LATC,ACCESS ; clear PortC output movlw B'01101000' ; MSB:LSB [LED,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] movwf TRISC,ACCESS ; PortC [0,1,2,4,7] output; [3,5,6] input ; PORTD/TRISD initialization - CF Card data bus. clrf LATD,ACCESS ; clear PortD output movlw B'00000000' ; set RC7-RC0 to outputs movwf TRISD,ACCESS ; PORTE/TRISE initialization - CF Card address bus. clrf LATE,ACCESS ; clear PortE output movlw B'00000000' ; set RE2-RE0 to outputs movwf TRISE,ACCESS ; Enable priority on interrupts. bsf RCON,IPEN,ACCESS ; enable interrupt priority levels ; Timer1 setup. bcf IPR1,TMR1IP,ACCESS ; set Timer1 interrupt to low priority movlw B'00000000' movwf T1CON,ACCESS ; pre & post scaler 1:1, timer off bcf PIR1,TMR1IF,ACCESS ; clear interrupt flag bsf PIE1,TMR1IE,ACCESS ; enable Timer1 interrupts movlw 0xFC ; FFFF-FCFF=0300h=d'768' cnts movwf TMR1H,ACCESS ; timer rollover every 768 instructions movlw 0xFF ; so, collect inst data every 625 usec movwf TMR1L,ACCESS ; (=1600 bytes/sec) clrf inst_flag,ACCESS ; clear instrumentation flag clrf timeout_cnt,ACCESS ; clear inst int timeout cntr movlw 0x45 ; initialize inst data to 45h ('E') movwf inst_data_b,ACCESS ; this is just a dat pattern value ; External interrupt INT1 setup, used end instrumentation activity. bcf INTCON2,INTEDG1,ACCESS ; interrupt on falling edge bcf INTCON3,INT1IP,ACCESS ; set INT1 to low priority, should be ; same priority as instrumentation event bcf INTCON3,INT1IF,ACCESS ; clear interrrupt flag bsf INTCON3,INT1IE,ACCESS ; enable interrupt clrf end_inst_fl,ACCESS ; clear flag. Bit 0: 1=end inst cf_init: ; Set BSR to CF-Ops variable bank. movlb CFOPS_GPR ; set BSR to CF-ops variables bank ; Initialize CF Card present logic. movlw NO_CARD ; start out assuming no CF card movwf error_code,BANKED movff CF_STATUS,portC_sh ; get value of portC bsf portC_sh,LED,BANKED ; turn error LED on movff portC_sh,CF_CONTROL ; using PortA shadow register ; Init fixed portion of CF read/write structure. movlw 0x01 movwf cf_rw_scnt,BANKED ; always read/write 1 sector at a time clrf cf_rw_cylhi,BANKED ; sector address limited to 16-bits movlw 0xE0 movwf cf_rw_head,BANKED ; set LBA addressing, high sec addr = 0 ; Start'em up! goto main ;****************************************************************************** ;***** Main Routine ***** ;****************************************************************************** ; main: ; ; verify CF Card present check_card: btfsc CF_STATUS,CD1,ACCESS ; check for CF Card present, LATC[5] goto check_card ; loop till card inserted ; clear no card error condition clrf error_code,BANKED ; clear error code movff CF_STATUS,portC_sh ; get value of portC bcf portC_sh,LED,BANKED ; turn error LED off movff portC_sh,CF_CONTROL ; using PortA shadow register ;***NOTE*** without the stanby mode below, the first read cmd does not work right. ; Probably issueing a RESET to the CF card first thing would work also. ; set CF to STANDBY mode movlw B'00000111' ; MSB:LSB [-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] movwf CF_CONTROL,ACCESS ; set control lines on PortA, STANDBY nop ; breakpoint opportunity ; Read CF Card info and setup for file write. call cf_setup_init ; Just before ready to begin instrumenting data make this call. call cf_file_write_init ; Enable interrupts and start Timer1. bsf INTCON,GIE,ACCESS bsf INTCON,PEIE,ACCESS ; enable low priority peripheral ints bsf T1CON,TMR1ON,ACCESS ; start Timer1, used to generate inst data ; Begin collecting instrumentation data. inst_loop: ; Always call 'cf_file_write' first thing in loop as it is the 'background' ; task that is emptying the GPR instrumentation data buffers to the CF card. call cf_file_write ; If inst data avail, write it to the GPR inst buffer. btfss inst_flag,0,ACCESS ; see if inst flag set goto check_quit ; No, check for user requested stop ; Following instruction alternates inst data between 45h ('E') and 54h ('T'). ; This gives a test pattern to data for examining the results. swapf inst_data_b,F,ACCESS ; Write instrumentation data byte to the GPR buffer. ; Copy inst data to active GPR bank. movff bnk_4_5_prt,FSR2H ; setup indirect address in active GPR bank movff inst_bytcnt,FSR2L movff inst_data_b,INDF2 ; move inst data to GPR bank ; Increment count of bytes written to active bank. incf inst_bytcnt,F,ACCESS ; Test if counter has rolled over, if so active bank is full. btfss STATUS,Z,ACCESS goto write_inst_done ; no rollover, nothing else to do ; Counter has rolled over meaning we have written 256 bytes, the active bank ; is full. So switch to the other bank. movlw 0x04 xorwf bnk_4_5_prt,W,ACCESS ; was 'bnk_4_5_prt' set to bank 4 btfss STATUS,Z,ACCESS ; Yes, so set active GPR bank to 5 goto set_active_4 ; No, so set active GPR bank to 4 ; Set active bank to GPR bank 5. movlw 0x05 movwf bnk_4_5_prt,ACCESS ; Test for timeout on emptying bank. If we are trying to write data ; to the CF card too fast this variable will still be set meaning we ; are missing some data. btfsc bank_4_full,0,ACCESS incf timeout_cnt,F,ACCESS ; Increment counter if timeout ; Set bank 4 full flag. bsf bank_4_full,0,ACCESS goto write_inst_done set_active_4: ; Set active bank to GPR bank 4. movlw 0x04 movwf bnk_4_5_prt,ACCESS ; Test for timeout on emptying bank. btfsc bank_5_full,0,ACCESS incf timeout_cnt,F,ACCESS ; Increment counter if timeout ; Set bank 5 full flag. bsf bank_5_full,0,ACCESS write_inst_done: ; Clear flag after writing inst data to buffer. bcf inst_flag,0,ACCESS ; reset inst data available flag check_quit: ; Check if user requested end to instrumentation. btfss end_inst_fl,0,ACCESS ; flag set? Bit 0: 1=end inst goto inst_loop ; No, so stay in loop ; User requested stop. ; To close instrumentation file and perform update of CF Card root directory ; and FAT structures make this call. call cf_file_close movlw USER_STOP call error_stop ; Does not return!!! ;****************************************************************************** ;****************************************************************************** ; ***** Subroutines ***** ;****************************************************************************** ;****************************************************************************** ; error_stop: ; Loads error code passed in WREG to error variable, turns on error LED, ; puts the CF Card in STANDBY mode, and performs a tight loop on last ; instruction, effectively stopping. ; ; Record error code. movwf error_code,BANKED ; Set CF to STANDBY mode and turn on LED. movlw B'10000111' ; MSB:LSB [LED,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] movwf CF_CONTROL,ACCESS ; set control lines on PortA, STANDBY ; effectively stops here in a tight loop goto $ ; stop ; cf_check_ready: ; ; Loops until the CF Card is ready for next command. ; CF_CONTROL (PortC) layout; [MSB:LSB]; [LED,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] ; btfss CF_STATUS,RDY,ACCESS ; check that CF card is ready, PORTC[3] goto cf_check_ready ; loop till CF card ready return ; cf_write: ; ; Writes one byte of data to the CF card I/O buffer. ; CF_CONTROL (PortC) layout; [MSB:LSB]; [X,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] ; CF Card register address passed in cf_io_reg. ; Write data passed in cf_io_data. ; clrf TRISD,ACCESS ; make data bus an output call cf_check_ready movff cf_io_reg,LATE ; put register address on LATE movff cf_io_data,LATD ; put data on data bus, LATD movff CF_STATUS,portC_sh ; get value of portC bcf portC_sh,CE1,BANKED ; strobe -CE1 line low bcf portC_sh,WE,BANKED ; strobe -WE line low movff portC_sh,CF_CONTROL ; using PortC shadow register ck_wr_done: btfss CF_STATUS,WAIT,ACCESS ; wait for CF done, PORTC[6] goto ck_wr_done movff CF_STATUS,portC_sh ; get value of portC bsf portC_sh,CE1,BANKED ; hold -CE1 line high bsf portC_sh,WE,BANKED ; hold -WE line high movff portC_sh,CF_CONTROL ; using PortA shadow register return ; cf_sec_rw_cmd: ; ; Reads/Writes a sector of CF card data. ; Command parameters should have already been loaded in cf_rw_structure. ; movlw d'6' movwf loop_cntr,BANKED ; will be writing 6 registers movlw d'2' movwf cf_rw_reg,BANKED ; starting with reg offset 2 lfsr FSR0,cf_rw_scnt ; point to cf_rw_structure cf_rw_loop: movff POSTINC0,cf_io_data ; load register data movff cf_rw_reg,cf_io_reg ; load register to load call cf_write incf cf_rw_reg,F,BANKED ; advance to next register decfsz loop_cntr,F,BANKED ; loop till all 6 written goto cf_rw_loop return ; cf_read: ; ; Reads one byte from the CF Card I/O buffer. ; Data is read from the 'data' register, CF register addr = 0. ; CF_CONTROL (PortC) layout; [MSB:LSB]; [X,-WAIT,-CD1,RESET,RDY,-WE,-OE,-CE1] ; movlw B'11111111' movwf TRISD,ACCESS ; make data bus an input clrf CF_ADDR,ACCESS ; clear A0 address line, LATE ; read one byte call cf_check_ready movff CF_STATUS,portC_sh ; get value of portC bcf portC_sh,CE1,BANKED ; strobe -CE1 line low bcf portC_sh,OE,BANKED ; strobe -OE line low movff portC_sh,CF_CONTROL ; using PortA shadow register ck_rd_done: btfss CF_STATUS,WAIT,ACCESS ; wait for CF done, PORTC[6] goto ck_rd_done movff CF_DATA_IN,cf_io_data ; save data byte movff CF_STATUS,portC_sh ; get value of portC bsf portC_sh,CE1,BANKED ; hold -CE1 line high bsf portC_sh,OE,BANKED ; hold -OE line high movff portC_sh,CF_CONTROL ; using PortA shadow register return ; load_table_params: ; ; Collects the CF Card data defined in ROM table. The table pointer registers ; have already been setup by the calling routine. Locates variables defined in ; the ROM table by offset into the CF card I/O buffer and copies the value to GPR. ; bsf EECON1,EEPGD,ACCESS ; access flash program memory movlw 0xFF movwf read_ptr_hi,BANKED ; init byte pointer to -1, will rollover to 0 movwf read_ptr_lo,BANKED ; on the first increment tbl_loop: tblrd *+ ; read table:byte_offset into TABLAT and inc ptr movff TABLAT,WREG ; WREG=table:byte_offset xorlw EOT ; compare table:byte_offset to EOT char btfsc STATUS,Z,ACCESS ; was last table:byte_offset the EOT goto end_tbl ; yes, so quit movff TABLAT,read_tgt_hi ; copy 16-bit offset to target tblrd *+ movff TABLAT,read_tgt_lo find_offset_byte: call cf_read ; read byte of CF ID data incf read_ptr_lo,F,BANKED ; increment 16-bit pointer btfsc STATUS,C,ACCESS ; low byte inc cause carry? incf read_ptr_hi,F,BANKED ; yes, so inc high byte call compr_ptr_to_tgt ; we there yet? btfss STATUS,Z,ACCESS ; if cmpr X=Y then Z=1 goto find_offset_byte ; No, read next CF id byte movlw 0x01 movwf bytecnt,BANKED ; 1 byte per read, 1 byte read ; Store CF ID value in GPR variables. movff cf_io_data,POSTINC0 ; Read number of CF ID data bytes for table entry. tblrd *+ ; throw away pad byte tblrd *+ ; read table:bytes into TABLAT and inc ptr movff TABLAT,WREG ; WREG=table:bytes subwf bytecnt,W,BANKED ; bytecnt = table:bytes ? btfsc STATUS,Z,ACCESS goto tbl_loop ; YES, get next table:word byte_loop: ; Get byte of CF Card data. call cf_read ; read byte of CF ID data incf read_ptr_lo,F,BANKED ; increment 16-bit pointer btfsc STATUS,C,ACCESS ; low byte inc cause carry? incf read_ptr_hi,F,BANKED ; yes, so inc high byte ; Store CF card value in GPR variables. movff cf_io_data,POSTINC0 ; Adjust count of bytes read. incf bytecnt,F,BANKED ; inc cnt of bytes read ; Test to see if we've read all the bytes required. movff TABLAT,WREG ; WREG=table:bytes subwf bytecnt,W,BANKED ; bytecnt = table:bytes ? btfss STATUS,Z,ACCESS goto byte_loop ; NO, get next CF ID data goto tbl_loop ; YES, so get next table entry end_tbl: return ; compr_ptr_to_tgt: ; ; Signed and unsigned 16 bit comparison routine: ; by David Cary 2001-03-30 ; Returns the correct flags (Z and C) to indicate the X=Y, X>Y, or X<=Y. ; Does not modify X or Y (X=read pointer; Y=read target). ; Results: if X=Y then Z=1. ; if X>Y then C=0. ; if X<=Y then C=1. movff read_ptr_hi,WREG subwf read_tgt_hi,W,BANKED ; subtract Y-X btfss STATUS,Z,ACCESS ; Are they equal? goto compr_ptr_end ; No movff read_ptr_lo,WREG ; yes, they are equal -- compare lo subwf read_tgt_lo,W,BANKED ; subtract Y-X compr_ptr_end: return ; comp_strings: ; ; Compares string in ROM with one in RAM: ; Length of string passed in WREG. ; Results: if X=Y then STATUS:Z=1, otherwise STATUS:Z=0. ; Result is set by XOR instruction. movwf bytecnt,BANKED ; initialize counter compr_str_loop: movff POSTINC0,WREG ; get char of RAM string tblrd *+ ; read table: get char of ROM string xorwf TABLAT,W,ACCESS ; compare RAM to ROM char btfss STATUS,Z,ACCESS ; if equal, compare till done goto compr_str_end ; no, so quit decfsz bytecnt,F,BANKED ; dec string length counter goto compr_str_loop ; compare next characters compr_str_end: return ; cf_load_bootsec_parms: ; ; Sends command to read the CF Card boot sector; sector number: 0. ; Sector zero contains the partition table at offset 0x1BE. ; ; setup sector number (LBA 7:0) register clrf cf_rw_snum,BANKED ; reading sector zero ; setup cylinder low (LBA 15:8) register clrf cf_rw_cyllo,BANKED ; reading sector zero ; setup comand register movlw RD_SEC_CMD ; get read sector command value movwf cf_rw_cmd,BANKED call cf_sec_rw_cmd ; Load Boot Sector/BIOS Param Block parameters defined in table structure 'bs_parm_tbl'. ; Setup indirect addr for bs_bio_parms variables. lfsr FSR0,pt_parms ; Setup TBLPTR for ROM reads. movlw upper pt_parm_tbl movwf TBLPTRU,ACCESS movlw high pt_parm_tbl movwf TBLPTRH,ACCESS movlw low pt_parm_tbl movwf TBLPTRL,ACCESS ; Load RAM variables defined in ROM table. call load_table_params ; load CF Card ID data defined in table pt_parm_tbl return ; cf_load_BIOS_parms: ; ; Send command to read the 1st partition Boot Sector/BIOS Parameter Block. ; ; setup sector number (LBA 7:0) register movff pt_ofset_ll,cf_rw_snum ; partition table offset (lwlb) ; setup cylinder low (LBA 15:8) register movff pt_ofset_lh,cf_rw_cyllo ; partition table offset (lwhb) ; setup comand register movlw RD_SEC_CMD ; get read sector command value movwf cf_rw_cmd,BANKED call cf_sec_rw_cmd ; Load Boot Sector/BIOS Param Block parameters defined in table structure 'bs_parm_tbl'. ; Setup indirect addr for bs_bio_parms variables. lfsr FSR0,bs_bio_parms ; Setup TBLPTR for ROM reads. movlw upper bs_parm_tbl movwf TBLPTRU,ACCESS movlw high bs_parm_tbl movwf TBLPTRH,ACCESS movlw low bs_parm_tbl movwf TBLPTRL,ACCESS ; Load RAM variables defined in ROM table. call load_table_params ; load CF Card ID data defined in table cf_parm_tbl return ; cf_verify_FAT16: ; ; Verifies the BPB file type = FAT16. ; ; Setup indirect addr for BPB file type variable. lfsr FSR0,bpb_fil_typ ; Setup TBLPTR for ROM reads. movlw upper fat16_str movwf TBLPTRU,ACCESS movlw high fat16_str movwf TBLPTRH,ACCESS movlw low fat16_str movwf TBLPTRL,ACCESS movlw FATSTRLEN ; get string length ; Load RAM variables defined in ROM table. call comp_strings ; see if strings match btfsc STATUS,Z,ACCESS ; successful (Z=0)? goto end_verify wrong_format: movlw BAD_FORMAT ; CF Card format not FAT16 call error_stop end_verify: return ; cf_read_sector: ; ; Read CF Flash sector, put in GPR banks: 4 & 5. ; ; setup comand register movlw RD_SEC_CMD ; get read sector command value movwf cf_rw_cmd,BANKED call cf_sec_rw_cmd ; copy sector of data to GPR banks 4 & 5, 256 bytes per bank movlw 0x02 ; twice through 256 byte loop movwf loop_cntr,BANKED ; Setup indirect addr for read data lfsr FSR0,0x400 ; point to GPR bank 4 ; Setup CF read register movlw DATA_REG movwf cf_io_reg,BANKED rd_bank_loop: ; Setup read counter. clrf bytecnt,BANKED ; 0 -> 0xFF inclusive = d'256' read_data_loop: call cf_read ; Store CF data value in GPR. movff cf_io_data,POSTINC0 decfsz bytecnt,F,BANKED goto read_data_loop ; finished bank 4 (256 bytes) now do GPR bank 5 decfsz loop_cntr,F,BANKED goto rd_bank_loop return ; cf_write_sector: ; ; Write sector of data in GPR banks: 4 & 5 to CF Card. ; ; setup comand register movlw WR_SEC_CMD ; get write sector command value movwf cf_rw_cmd,BANKED call cf_sec_rw_cmd ; write data in GPR banks 4 & 5, 256 bytes per bank movlw 0x02 ; twice through 256 byte loop movwf loop_cntr,BANKED ; Setup indirect addr for write data lfsr FSR0,0x400 ; point to GPR bank 4 ; Setup CF write register movlw DATA_REG movwf cf_io_reg,BANKED wr_bank_loop: ; Setup write counter clrf bytecnt,BANKED ; 0 -> 0xFF inclusive = d'256' write_data_loop: ; write data to CF Card movff POSTINC0,cf_io_data call cf_write decfsz bytecnt,F,BANKED goto write_data_loop ; finished bank 4 (256 bytes) now do GPR bank 5 decfsz loop_cntr,F,BANKED goto wr_bank_loop return ; cf_find_file: ; ; Loads the first sector of the root directory and searches for filename. ; If filename not found an error condition is raised. ; ; Compute first segment of FAT Root Directory, offset by 1st partion offset. ; FirstRootDirSecNum = BPB_ResvdSecCnt + (BPB_NumFATs * BPB_FATSz16) + ; partition offset movff bpb_num_fat,t_16bit_lb ; make 16-bit from 8-bit clrf t_16bit_hb,BANKED lfsr FSR0,t_16bit_lb lfsr FSR1,bpb_fsiz_lb call mult16x16U ; product = (BPB_NumFATs * BPB_FATSz16) lfsr FSR0,prod_lwlb ; low word of product lfsr FSR1,bpb_res_lb call add16U ; sum(1) = BPB_ResvdSecCnt + (BPB_NumFATs * BPB_FATSz16) movff sum_lb,t_16bit_lb ; move product to temp variable movff sum_hb,t_16bit_hb lfsr FSR0,t_16bit_lb lfsr FSR1,pt_ofset_ll call add16U ; sum(2) = pt_ofset_ll:lh + sum(1) movff sum_lb,frdirsec_lb ; save: FirstRootDirSecNum movff sum_hb,frdirsec_hb ; Get the root directory. Send command to read the 1st sector of root dir. ; setup sector number (LBA 7:0) register movff frdirsec_lb,cf_rw_snum ; root dir sector (lb) ; setup cylinder low (LBA 15:8) register movff frdirsec_hb,cf_rw_cyllo ; root dir sector (hb) call cf_read_sector ; Search 1st sector of root dir for 'file_name'. ; Each directory entry is 32 bytes, so 512/32=16 entries per sector movlw d'16' movwf loop_cntr ; setup loop counter movlw 0x04 movwf t_16bit_hb,BANKED ; set 16-bit variable to Bank4 clrf t_16bit_lb,BANKED root_dir_loop: ; Setup indirect addr for root directory sector entries ; lfsr FSR0,bpb_fil_typ movff t_16bit_hb,FSR0H movff t_16bit_lb,FSR0L ; Setup TBLPTR for ROM reads movlw upper file_name movwf TBLPTRU,ACCESS movlw high file_name movwf TBLPTRH,ACCESS movlw low file_name movwf TBLPTRL,ACCESS movlw FILSTRLEN ; get string length ; Load RAM variables defined in ROM table. call comp_strings ; see if strings match btfsc STATUS,Z,ACCESS ; successful (Z=0)? goto end_find_file ; increment variable to access next 32-byte entry movlw d'32' addwf t_16bit_lb,F,BANKED btfsc STATUS,C,ACCESS ; did add cause carry incf t_16bit_hb,F,BANKED ; yes, so inc high byte of variable ; test for end of sector decfsz loop_cntr,F,BANKED goto root_dir_loop ; filename not found in first sector movlw FIL_NOT_FND ; filename not found call error_stop end_find_file: ; At this point t_16bit_lb:hb holds offset of 'filename' entry in root ; directory sector 0, including the 0x400 offset for bank 4. movff t_16bit_lb,rdf_off_lb ; save offset for later movff t_16bit_hb,rdf_off_hb return ; cf_verify_file_empty: ; ; The last six bytes (cluster & filesize) of the directory entry should ; be zero. These are at offset 0x1A from the beginning of the entry. ; At this point t_16bit_lb:hb holds offset of 'filename' entry in root ; directory sector 0, including the 0x400 offset for bank 4. movlw 0x1A ; add offset to dir entry offset value addwf t_16bit_lb,F,BANKED btfsc STATUS,C,ACCESS ; did add cause carry incf t_16bit_hb,F,BANKED ; yes, so inc high byte of variable movff t_16bit_hb,FSR0H ; setup indirect addr pointer movff t_16bit_lb,FSR0L movlw d'6' movwf loop_cntr ; setup loop counter verfy_empty_loop: movff POSTINC0,WREG ; get dir entry byte xorlw 0x00 btfss STATUS,Z,ACCESS ; test if zero goto not_empty_err decfsz loop_cntr,F,BANKED goto verfy_empty_loop goto end_verify_empty not_empty_err: ; filename not empty movlw FILE_IN_USE ; filename already has data call error_stop end_verify_empty: return ; cf_comp_rootdir_size: ; ; Compute the root directory size (in sectors) and the first data sector. ; RootDirSectors = ((BPB_RootEntCnt * 32) + ( BPB_BytesPerSec - 1))/BPB_BytesPerSec lfsr FSR0,bpb_bps_lb ; init ptr to minuend clrf t_16bit_hb,BANKED movlw d'1' movwf t_16bit_lb,BANKED ; put 1 in 16-bit temp variable lfsr FSR1,t_16bit_lb ; init ptr to subtrahend call sub16U ; diff = (BPB_BytesPerSec - 1) clrf t_16bit_hb,BANKED movlw d'32' movwf t_16bit_lb,BANKED ; put 32 in 16-bit temp variable lfsr FSR0,t_16bit_lb lfsr FSR1,bpb_rent_lb call mult16x16U ; product = (BPB_RootEntCnt * 32) lfsr FSR0,prod_lwlb ; low word of product lfsr FSR1,diff_lb call add16U ; sum = ((BPB_RootEntCnt * 32) + ; ( BPB_BytesPerSec - 1)) lfsr FSR0,bpb_bps_lb ; init ptr to denominator lfsr FSR1,sum_lb ; init ptr to numerator call div16by16U ; quotient = RootDirSectors movff quotient_lb,rtdirsiz_lb ; save result in GPR movff quotient_hb,rtdirsiz_hb ; save result in GPR return ; cf_comp_1st_data_sect: ; ; The start of the CF card data region, the first sector of cluster 2. ; FirstDataSector = BPBResvdSecCnt + (BPB_NumFATs * BPB_FATSz16) + ; RootDirSectors + partition offset movff bpb_num_fat,t_16bit_lb ; make 16-bit from 8-bit clrf t_16bit_hb,BANKED lfsr FSR0,t_16bit_lb lfsr FSR1,bpb_fsiz_lb call mult16x16U ; product = (BPB_NumFATs * BPB_FATSz16) lfsr FSR0,prod_lwlb ; low word of product lfsr FSR1,bpb_res_lb call add16U ; sum(1) = BPB_ResvdSecCnt + (BPB_NumFATs * BPB_FATSz16) movff sum_lb,t_16bit_lb ; move product to temp variable movff sum_hb,t_16bit_hb lfsr FSR0,t_16bit_lb lfsr FSR1,rtdirsiz_lb call add16U ; sum(2) = sum(1) + BPBResvdSecCnt movff sum_lb,t_16bit_lb ; move product to temp variable movff sum_hb,t_16bit_hb lfsr FSR0,t_16bit_lb lfsr FSR1,pt_ofset_ll call add16U ; sum(3) = pt_ofset_ll:lh + sum(2) movff sum_lb,fdatasec_lb ; save: FirstDataSector movff sum_hb,fdatasec_hb movff fdatasec_lb,fil_dsec_lb ; set 1st file sector to 1st data sector movff fdatasec_hb,fil_dsec_hb return ; cf_close_dirupdt: ; ; Update root directory file entry. ; ; Get the root directory, put in GPR banks 4&5. ; setup sector number (LBA 7:0) register movff frdirsec_lb,cf_rw_snum ; root dir sector (lb) ; setup cylinder low (LBA 15:8) register movff frdirsec_hb,cf_rw_cyllo ; root dir sector (hb) call cf_read_sector ; DIR_FstClusLo:low byte is at offset 0x1A into the directory entry. clrf t_16bit_hb,BANKED movlw 0x1A movwf t_16bit_lb,BANKED ; put 0x1A in 16-bit temp variable lfsr FSR0,t_16bit_lb lfsr FSR1,rdf_off_lb ; addr of file entry in GPR call add16U ; sum = addr(file in root dir + ; offset to first cluster num) movff sum_lb,FSR0L ; setup indirect address cluster movff sum_hb,FSR0H movlw d'2' ; we are starting file at cluster 2 movff WREG,INDF0 ; put cluster number 4 in directory entry ; DIR_FileSize:low byte is at offset 0x1C into the directory entry. clrf t_16bit_hb,BANKED movlw 0x1C movwf t_16bit_lb,BANKED ; put 0x1C in 16-bit temp variable lfsr FSR0,t_16bit_lb lfsr FSR1,rdf_off_lb ; addr of file entry in GPR call add16U ; sum = addr(file in root dir + ; offset to file size) movff sum_lb,FSR0L ; setup indirect address to file size movff sum_hb,FSR0H ; move 4 bytes of filesize to directory entry movff fil_size_ll,POSTINC0 movff fil_size_lh,POSTINC0 movff fil_size_hl,POSTINC0 movff fil_size_hh,POSTINC0 ; Write root directory update back to CF Card. ; setup sector number (LBA 7:0) register movff frdirsec_lb,cf_rw_snum ; root dir sector (lb) ; setup cylinder low (LBA 15:8) register movff frdirsec_hb,cf_rw_cyllo ; root dir sector (hb) call cf_write_sector return ; cf_close_clusupdt: ; ; Update the FAT cluster chains for the file, FAT2 is duplicate of FAT1. ; ; Compute FAT1 starting sector number = reserved sectors + 1st partion offset lfsr FSR0,bpb_res_lb lfsr FSR1,pt_ofset_ll call add16U ; sum = pt_ofset + reserved sectors movff sum_lb,ffat1sec_lb movff sum_hb,ffat1sec_hb ; Compute FAT2 starting sector number lfsr FSR0,ffat1sec_lb lfsr FSR1,bpb_fsiz_lb call add16U ; sum = FAT1 start sector + FAT sector size movff sum_lb,ffat2sec_lb movff sum_hb,ffat2sec_hb ; Compute cluster count for file cluster chain. ; cluster count = file data sectors / sectors per cluster movff bpb_spc,t_16bit_lb ; convert 8-bit to 16-bit number clrf t_16bit_hb,BANKED lfsr FSR0,t_16bit_lb ; init ptr to denominator lfsr FSR1,fil_swrt_lb ; init ptr to numerator call div16by16U ; quotient = RootDirSectors movff quotient_lb,fcluscnt_lb ; save result in RAM movff quotient_hb,fcluscnt_hb ; save result in RAM ; round cluster count up if any remainder clrf WREG,ACCESS xorwf remain_lb,W,BANKED btfss STATUS,Z,ACCESS ; test if remain_lb = 0 goto clus_round_up ; no, so round up clrf WREG,ACCESS xorwf remain_hb,W,BANKED btfss STATUS,Z,ACCESS ; test if remain_hb = 0 goto clus_round_up ; no, so round up goto build_chain clus_round_up: incf fcluscnt_lb,F,BANKED btfsc STATUS,C,ACCESS ; carry out? incf fcluscnt_hb,F,BANKED ; yes, so inc high byte build_chain: ; Get first sector of FAT1, put in GPR banks 4&5. ; setup sector number (LBA 7:0) register movff ffat1sec_lb,cf_rw_snum ; setup cylinder low (LBA 15:8) register movff ffat1sec_hb,cf_rw_cyllo call cf_read_sector ; Set first cluster for file = 2, cluster 0 & 1 are reserved. movlw d'2' movwf filclus_lb,BANKED clrf filclus_hb,BANKED ; Set clusters written to sector count = 2 (cluster 0 & 1 are reserved). movlw d'2' movwf clus_swc_lb,BANKED clrf clus_swc_hb,BANKED begin_fat_segment: ; Initialize FAT cluster offset to beginning of GPR bank 4. movlw d'4' movwf fatcloff_hb,BANKED clrf fatcloff_lb,BANKED ; offset = 0x0400 ; Compute FAT cluster offset (2-bytes per cluster). movlw d'2' movwf t_16bit_lb,BANKED clrf t_16bit_hb,BANKED lfsr FSR0,t_16bit_lb lfsr FSR1,clus_swc_lb call mult16x16U ; product = 2 * clusters written lfsr FSR0,prod_lwlb lfsr FSR1,fatcloff_lb call add16U ; sum = GRP4 offset + (2 * clusters written) movff sum_lb,fatcloff_lb movff sum_hb,fatcloff_hb movff fatcloff_lb,FSR0L ; setup indirect address for chain link movff fatcloff_hb,FSR0H write_chain_loop: ; Test case of only 1 cluster for file, or, one cluster left to write. decf fcluscnt_lb,W,BANKED ; subtract 1 from low byte btfss STATUS,Z,ACCESS ; result zero? goto more_than_one ; no, so create link clrf WREG,ACCESS ; yes, see if high byte = 0 xorwf fcluscnt_hb,W,BANKED btfsc STATUS,Z,ACCESS ; file cluster count high byte = 0? goto write_eoc ; yes, so write end-of-chain mark more_than_one: ; Each link of cluster chain points to next link, so, increment file cluster number. incf filclus_lb,F,BANKED btfsc STATUS,C,ACCESS incf filclus_hb,F,BANKED ; Write next file cluster number to current FAT cluster entry. movff filclus_lb,POSTINC0 ; write pointer to next link movff filclus_hb,POSTINC0 ; Decrement count of file clusters. decf fcluscnt_lb,F,BANKED btfss STATUS,C,ACCESS ; borrow\ set? decf fcluscnt_hb,F,BANKED ; yes, so adjust high byte ; Increment count of cluster chain entries written to FAT segment. incf clus_swc_lb,F,BANKED ; max value is 256 ; See if 256 chain entries written (counter rollover), if so, sector is full. btfss STATUS,Z,ACCESS ; test if = 256 goto write_chain_loop ; no, so continue writing to this sector of FAT ; Write sector of FAT data to CF Card FAT1 from GPR banks 4&5. ; setup sector number (LBA 7:0) register movff ffat1sec_lb,cf_rw_snum ; setup cylinder low (LBA 15:8) register movff ffat1sec_hb,cf_rw_cyllo call cf_write_sector ; Write sector of FAT data to CF Card FAT2 from GPR banks 4&5. ; setup sector number (LBA 7:0) register movff ffat2sec_lb,cf_rw_snum ; setup cylinder low (LBA 15:8) register movff ffat2sec_hb,cf_rw_cyllo call cf_write_sector ; Increment to next sector of FAT1 & FAT2. incf ffat1sec_lb,F,BANKED ; inc FAT1 sector count btfsc STATUS,Z,ACCESS incf ffat1sec_hb,F,BANKED incf ffat2sec_lb,F,BANKED ; inc FAT2 sector count btfsc STATUS,Z,ACCESS incf ffat2sec_hb,F,BANKED ; Get next sector of FAT1, put in GPR banks 4&5. ; setup sector number (LBA 7:0) register movff ffat1sec_lb,cf_rw_snum ; setup cylinder low (LBA 15:8) register movff ffat1sec_hb,cf_rw_cyllo call cf_read_sector ; Clear count of FAT entries written to segment. clrf clus_swc_lb,BANKED goto begin_fat_segment write_eoc: ; We're at the last cluster of the chain, so write end-of-chain to cluster. movlw 0xFF movff WREG,POSTINC0 ; write eoc mark: 0xFFFF movff WREG,POSTINC0 ; Write sector of FAT data to CF Card FAT1 from GPR banks 4&5. ; setup sector number (LBA 7:0) register movff ffat1sec_lb,cf_rw_snum ; setup cylinder low (LBA 15:8) register movff ffat1sec_hb,cf_rw_cyllo call cf_write_sector ; Write sector of FAT data to CF Card FAT2 from GPR banks 4&5. ; setup sector number (LBA 7:0) register movff ffat2sec_lb,cf_rw_snum ; setup cylinder low (LBA 15:8) register movff ffat2sec_hb,cf_rw_cyllo call cf_write_sector return ; cf_file_write_init: ; ; Initializes instrumentation process variables and sends a command to ; the CF card to write the first data sector. ; Zero instrumentation file variables. clrf fil_size_ll,BANKED ; filesize clrf fil_size_lh,BANKED clrf fil_size_hl,BANKED clrf fil_size_hh,BANKED clrf fil_swrt_lb,BANKED ; file sectors written clrf fil_swrt_hb,BANKED clrf fil_sbwc_lb,BANKED ; sector bytes written count clrf fil_sbwc_hb,BANKED ; Initialize instrumentation interrupt variables. clrf bank_4_full,ACCESS ; init inst data banks to empty clrf bank_5_full,ACCESS ; init inst data banks to empty clrf inst_bytcnt,ACCESS ; inst data bytes written to bank movlw 0x04 movwf bnk_4_5_prt,ACCESS ; start writing to bank 4 ; Send command to write the first data sector. ; setup sector number (LBA 7:0) register movff fil_dsec_lb,cf_rw_snum ; 1st data sector for file(lwlb) ; setup cylinder low (LBA 15:8) register movff fil_dsec_hb,cf_rw_cyllo ; 1st data sector for file (lwhb) ; setup comand register movlw WR_SEC_CMD ; get write sector command value movwf cf_rw_cmd,BANKED call cf_sec_rw_cmd return ; cf_file_write: ; ; Writes one byte of instrumentation data to file. When the max file size ; is reached the file close and CF card file system updates are performed. ; The file size is a 4 byte number; the bytes being hh:hl:lh:ll. The max ; file size test below looks at a single bit of byte 'hl'. ; hh:hl:lh:ll ; Possibilities are: bit 0 set = 00:01:00:00h (65,536) bytes, ; bit 1 set = 00:02:00:00h (131,072) bytes, ; bit 2 set = 00:04:00:00h (262,144) bytes, ; bit 3 set = 00:08:00:00h (524,288) bytes, etc. ; This code mas file size = 02:00:00:00h (33,554,432) bytes. ; Windows max file size = FF:FF:FF:FFh (4,294,967,295) bytes. ; ; Limitation of file size for the code is that I have only implemented 16-bit ; counters for counting sectors. 4GB file requires 8,388,607 sectors! ; ; In code below, the max file size is 0x10000 (65,536) bytes; 128 sectors. ; ; Test for max file size. btfsc fil_size_hl,0,BANKED ; Test if max filesize reached goto close_file ; Current sector byte count = 512? btfss fil_sbwc_hb,1,BANKED ; 511=1FFh, 512=200h so check bit 1 ; of counter high byte (bit cnt zero based) goto cf_file_wbyte ; No, write next byte to file ; Current sector full, so start new sector. ; Increment count of file sectors. incf fil_swrt_lb,F,BANKED ; inc count of sectors written btfsc STATUS,C,ACCESS ; carry on increment incf fil_swrt_hb,F,BANKED ; yes, fixup high byte for carry ; Add 1 to fil_dsec_lb:hb as address of next file sector. incf fil_dsec_lb,F,BANKED ; setup for next data sector btfsc STATUS,C,ACCESS ; carry on increment? incf fil_dsec_hb,F,BANKED ; yes, fixup high byte for carry ; Zero the sector byte count. clrf fil_sbwc_lb,BANKED clrf fil_sbwc_hb,BANKED ; Send command to write the next data sector. ; setup sector number (LBA 7:0) register movff fil_dsec_lb,cf_rw_snum ; data sector (lwlb) ; setup cylinder low (LBA 15:8) register movff fil_dsec_hb,cf_rw_cyllo ; data sector (lwhb) ; setup comand register movlw WR_SEC_CMD ; get write sector command value movwf cf_rw_cmd,BANKED call cf_sec_rw_cmd cf_file_wbyte: ; We started writing to file when bank 4 was full. After we empty bank 4 we ; start on bank 5, etc. Continue emptying the current bank until it is empty ; before switching to the other bank. ; Determine which GPR bank we are currently emptying. ; bank4: 'fil_sbwc_hb' = 00; 'fil_sbwc_lb' = 00-FF ; bank5: 'fil_sbwc_hb' = 01; 'fil_sbwc_lb' = 00-FF ; rollover to 'fil_sbwc_hb' = 02; 'fil_sbwc_lb' = 00 (at completion of ; bank 5 is handled above) ; movf fil_sbwc_hb,W,BANKED ; movf causes Z status update btfss STATUS,Z,ACCESS ; 'fil_sbwc_hb' = 0? goto current_bank5 ; No - so we are working bank 5 current_bank4: btfss bank_4_full,0,ACCESS ; bank 4 full? goto file_write_done ; No, so wait for data ; Bank 4 full, so work on emptying it. movlw 0x04 ; setup indirect address to get data movwf FSR0H,ACCESS ; byte from GPR bank goto get_data current_bank5: btfss bank_5_full,0,ACCESS ; bank 5 full? goto file_write_done ; No, so wait for data ; Bank 5 full, so work on emptying it. movlw 0x05 ; setup indirect address to get data movwf FSR0H,ACCESS ; byte from GPR bank get_data: movff fil_sbwc_lb,FSR0L ; counter 'fil_sbwc_lb' will run from 0-FFh ; twice, once for each bank ; Write byte of data to CF card instrumentation file. movff INDF0,cf_io_data ; get the data byte from the GPR bank clrf cf_io_reg,BANKED call cf_write ; Increment count of bytes written to sector. incf fil_sbwc_lb,F,BANKED btfsc STATUS,C,ACCESS ; carry on increment? incf fil_sbwc_hb,F,BANKED ; yes, fixup high byte for carry ; Increment count of bytes written to file. incf fil_size_ll,F,BANKED btfsc STATUS,C,ACCESS ; carry on increment? incf fil_size_lh,F,BANKED ; yes, fixup high byte for carry btfsc STATUS,C,ACCESS ; carry on increment? incf fil_size_hl,F,BANKED ; yes, fixup high byte for carry btfsc STATUS,C,ACCESS ; carry on increment? incf fil_size_hh,F,BANKED ; yes, fixup high byte for carry ; Test to see if done emptying data bank. movf fil_sbwc_lb,W,BANKED ; rollover from FFh to 00h? btfss STATUS,Z,ACCESS ; low byte of counter = 00h? goto file_write_done ; No, so we're done ; Done emptying GPR bank, clear 'buffer_x_full' flag for bank. ; If 'fil_sbwc_hb' = 01h we just finished bank 4 ; If 'fil_sbwc_hb' = 02h we just finished bank 5 movlw 0x01 xorwf fil_sbwc_hb,W,BANKED btfss STATUS,Z,ACCESS ; is 'fil_sbwc_hb' = 01h goto clear_5 ; No, clear bank 5 flag bcf bank_4_full,0,ACCESS ; Yes, clear bank 4 flag goto file_write_done clear_5: bcf bank_5_full,0,ACCESS file_write_done: return close_file: ; Perform CF Card instrumentation data file close and stop. call cf_file_close movlw FILE_FULL call error_stop ; cf_file_close: ; ; Update root directory and FATs with instrumentation file information. ; If user terminated instrumentation prior to first data byte being ; written to the file, do nothing, CF card file system remains with ; zero length for filename. ; ; Check if user requested instrumentation termination prior to any ; instrumentation data being collected. Test for filesize = 0. clrf WREG,ACCESS xorwf fil_size_ll,W,BANKED ; low:low byte=0? btfss STATUS,Z,ACCESS goto close_continue ; No, so something in file xorwf fil_size_lh,W,BANKED ; low:high byte=0? btfss STATUS,Z,ACCESS goto close_continue ; No, so something in file xorwf fil_size_hl,W,BANKED ; high:low byte=0? btfss STATUS,Z,ACCESS goto close_continue ; No, so something in file xorwf fil_size_hh,W,BANKED ; high:high byte=0? btfss STATUS,Z,ACCESS goto close_continue ; No, so something in file goto close_end ; Yes, filesize = 0 close_continue: ; Be sure last sector is full and therefore written. btfsc fil_sbwc_hb,1,BANKED ; 511=1FFh, 512=200h so check bit 2 ; of counter high byte goto updt_directory ; Yes, continue to dir update ; Finish writing to last sector. clrf inst_data_b,ACCESS ; finish by writing 0x00's cf_close_finish: ; Write byte of data to instrumentation file. movff inst_data_b,cf_io_data clrf cf_io_reg,BANKED call cf_write ; Increment count of bytes written to sector. incf fil_sbwc_lb,F,BANKED btfsc STATUS,C,ACCESS ; carry on increment? incf fil_sbwc_hb,F,BANKED ; yes, fixup high byte for carry ; Full yet? btfss fil_sbwc_hb,1,BANKED ; 511=1FFh, 512=200h so check bit 2 ; of counter high byte goto cf_close_finish ; No, continue to fill segment ; Increment count of file sectors. incf fil_swrt_lb,F,BANKED ; inc count of sectors written btfsc STATUS,C,ACCESS ; carry on increment incf fil_swrt_hb,F,BANKED ; yes, fixup high byte for carry updt_directory: ; Update root directory entry for instrumentation file. call cf_close_dirupdt ; Update FAT1 & FAT2 with instrumentation file cluster chains. call cf_close_clusupdt close_end: return ; cf_setup_init: ; ; Load the CF card partition table info. call cf_load_bootsec_parms ; Load the BIOS Parameter Block for card partition. call cf_load_BIOS_parms ; Verify the card format is FAT16. call cf_verify_FAT16 ; Find the inst data file 'PIC_DATA.DAT' in the root directory. call cf_find_file ; Verify the data file is empty. call cf_verify_file_empty ; Compute the root directory size in sectors (needed for next computation). call cf_comp_rootdir_size ; Compute sector number for first data sector of partition. call cf_comp_1st_data_sect return ; ;****************************************************************************** ; 16-bit x 16-bit unsigned integer multiplication routine ;****************************************************************************** mult16x16U: ; ; Routine length 32 line; time 129 to 228 cycles ; This program looks at the lsb of a1 to decide whether to add b1 to prod_lwhb ; and b2 to prod_hwlb, with appropriate carrys. ; It then looks at the lsb of a2 to decide whether to add b1 to prod_hwlb and ; b2 to prod_hwhb, again with appropriate carrys. ; The rotates then only have to be done 8 times ; ; This is uses slightly more program but takes a little less time than ; a routine that performs one 16 bit addition per rotate and 16 rotates ; ; Code from malin@onspec.co.uk ; Multiple byte addition routine from Microchip AN617 ; Result registers used as loop counter from Bob Fehrenbach & Scott Dattalo ; movff BSR,bsr_temp ; save source BSR ; Set BSR to math variable bank movlb MATH_GPR ; set BSR to math variable bank ; load multiplication operands movff POSTINC0,a1 ; load multiplicand variable L:H movff POSTINC0,a2 movff POSTINC1,b1 ; load multiplier variable L:H movff POSTINC1,b2 clrf prod_hwhb,BANKED clrf prod_hwlb,BANKED clrf prod_lwhb,BANKED movlw 0x80 movwf prod_lwlb,BANKED nextbit: rrcf a2,F,BANKED rrcf a1,F,BANKED btfss STATUS,C,ACCESS goto nobit_l movf b1,W,BANKED addwf prod_lwhb,F,BANKED movf b2,W,BANKED btfsc STATUS,C,ACCESS incfsz b2,W,BANKED addwf prod_hwlb,F,BANKED btfsc STATUS,C,ACCESS incf prod_hwhb,F,BANKED bcf STATUS,C,ACCESS nobit_l: btfss a1,7,BANKED goto nobit_h movf b1,W,BANKED addwf prod_hwlb,F,BANKED movf b2,W,BANKED btfsc STATUS,C,ACCESS incfsz b2,W,BANKED addwf prod_hwhb,F,BANKED nobit_h: rrcf prod_hwhb,F,BANKED rrcf prod_hwlb,F,BANKED rrcf prod_lwhb,F,BANKED rrcf prod_lwlb,F,BANKED btfss STATUS,C,ACCESS goto nextbit ; restore BSR movff bsr_temp,BSR ; restore source BSR return ;****************************************************************************** ; 16-bit by 16-bit unsigned integer division routine ;****************************************************************************** div16by16U: ; ; Division : accB(16 bits) / accA(16 bits) -> accB(16 bits) with ; Remainder in accC (16 bits) ; (a) Load the Denominator in location accAhi & accAlo ( 16 bits ) ; (b) Load the Numerator in location accBhi & accBlo ( 16 bits ) ; (c) CALL D_div ; (d) The 16 bit result is in location accBhi & accBlo ; (e) The 16 bit Remainder is in locations accChi & accClo ; ; Performance : ; Program Memory : 037 ; Clock Cycles : 310 ; movff BSR,bsr_temp ; save source BSR ; Set BSR to math variable bank movlb MATH_GPR ; set BSR to math variable bank ; load division operands movff POSTINC0,accAlo ; load denominator variable L:H movff POSTINC0,accAhi movff POSTINC1,accBlo ; load numerator variable L:H movff POSTINC1,accBhi movlw .16 ; for 16 shifts movwf math_temp,BANKED movff accBhi,accDhi ; move ACCb to ACCd movff accBlo,accDlo clrf accBhi,BANKED clrf accBlo,BANKED clrf accChi,BANKED clrf accClo,BANKED dloop: bcf STATUS,C,ACCESS rlcf accDlo,F,BANKED rlcf accDhi,F,BANKED rlcf accClo,F,BANKED rlcf accChi,F,BANKED movff accAhi,WREG subwf accChi,W,BANKED ; check if a>c btfss STATUS,Z,ACCESS goto nochk movff accAlo,WREG subwf accClo,W,BANKED ; if msb equal then check lsb nochk: btfss STATUS,C,ACCESS ; carry set if c>a goto nogo movff accAlo,WREG ; c-a into c subwf accClo,F,BANKED btfss STATUS,C,ACCESS decf accChi,F,BANKED movff accAhi,WREG subwf accChi,F,BANKED bsf STATUS,C,ACCESS ; shift a 1 into b (result) nogo: rlcf accBlo,F,BANKED rlcf accBhi,F,BANKED decfsz math_temp,F,BANKED ; loop untill all bits checked goto dloop ; restore BSR movff bsr_temp,BSR ; restore source BSR return ;****************************************************************************** ; 16-bit - 16-bit unsigned integer subtraction routine ;****************************************************************************** sub16U: ; ; Subtraction: DST(16-bit) = DST(16-bit) - SRC(16-bit) ; ; DTS is replaced, SRC is preserved ; Carry is set correctly, Zero flag is not valid ; movff BSR,bsr_temp ; save source BSR ; Set BSR to math variable bank movlb MATH_GPR ; set BSR to math variable bank ; load division operands movff POSTINC0,sub_dst_lb ; load minuend variable L:H movff POSTINC0,sub_dst_hb movff POSTINC1,sub_src_lb ; load subtrahend variable L:H movff POSTINC1,sub_src_hb ; perform subtraction movff sub_src_lb,WREG ; get low byte of subtrahend subwf sub_dst_lb,F,BANKED ; subtract DST(low) - SRC(low) movff sub_src_hb,WREG ; get high byte of subtrahend btfss STATUS,C,ACCESS ; if there was a borrow, rather than incf sub_src_hb,W,BANKED ; dec high byte of DST we inc SRC subwf sub_dst_hb,F,BANKED ; sub high byte and we're done ; restore BSR movff bsr_temp,BSR ; restore source BSR return ;****************************************************************************** ; 16-bit + 16-bit unsigned integer addition routine ;****************************************************************************** add16U: ; ; Addition: DST(16-bit) = DST(16-bit) + SRC(16-bit) ; ; DTS is replaced, SRC is preserved ; Carry is set correctly ; movff BSR,bsr_temp ; save source BSR ; Set BSR to math variable bank movlb MATH_GPR ; set BSR to math variable bank ; load division operands movff POSTINC0,add_dst_lb ; load minuend variable L:H movff POSTINC0,add_dst_hb movff POSTINC1,add_src_lb ; load subtrahend variable L:H movff POSTINC1,add_src_hb ; perform addition movff add_src_lb,WREG ; get low byte of source addwf add_dst_lb,F,BANKED ; add DST(low) + SRC(low) movff add_src_hb,WREG ; get high byte of source btfsc STATUS,C,ACCESS ; check for carry on low byte add incf add_src_hb,W,BANKED ; inc high byte for carry addwf add_dst_hb,F,BANKED ; add DST(high) + SRC(high) ; restore BSR movff bsr_temp,BSR ; restore source BSR return end **** Linker Script file ****
// File: CF_DB_Inst.lkr
// Linker script for program CF_DB_Inst.ASM
LIBPATH .
CODEPAGE NAME=page START=0x0 END=0x7FFF
CODEPAGE NAME=idlocs START=0x200000 END=0x200007 PROTECTED
CODEPAGE NAME=config START=0x300000 END=0x30000D PROTECTED
CODEPAGE NAME=devid START=0x3FFFFE END=0x3FFFFF PROTECTED
CODEPAGE NAME=eedata START=0xF00000 END=0xF000FF PROTECTED
ACCESSBANK NAME=accessram START=0x0 END=0x7F
DATABANK NAME=gpr0 START=0x80 END=0xFF
DATABANK NAME=gpr1 START=0x100 END=0x1FF
DATABANK NAME=gpr2 START=0x200 END=0x2FF
DATABANK NAME=gpr3 START=0x300 END=0x3FF
DATABANK NAME=gpr4 START=0x400 END=0x4FF
DATABANK NAME=gpr5 START=0x500 END=0x5FF
ACCESSBANK NAME=accesssfr START=0xF80 END=0xFFF PROTECTED
SECTION NAME=acs_vars RAM=accessram
SECTION NAME=prog_vars RAM=gpr3
SECTION NAME=cfops_vars RAM=gpr3
SECTION NAME=pt_vars RAM=gpr3
SECTION NAME=bs_bio_vars RAM=gpr3
SECTION NAME=math_vars RAM=gpr3
SECTION NAME=pt_prm_tbl ROM=page
SECTION NAME=bs_prm_tbl ROM=page
SECTION NAME=pgm_strings ROM=page
**** PIC to CF Card Interface ****
CF-PIC Interface – Using PC Card Common Memory Mode
Pin Name Action
1 GND tie to ground
2-6 D03-D07 I/O, connect to PIC, PORTD[03:07]
7 -CE1 byte/word mode, connect to PIC, PORTC[0]
8 A10 tie to ground
9 -OE Output Enable strobe, connect to PIC, PORTC[1]
10-12 A09-A07 tie to ground
13 VCC tie to +5V
14-17 A06-A03 tie to ground
18-20 A02-A00 register address lines, connect to PIC, PORTE[0:2]
21-23 D00-D02 I/O, connect to PIC, PORTD[00:02]
24 WP leave unconnected
25 -CD2 leave unconnected
26 -CD1 CF Present, connect to PIC, PORTC[5], also to 10K resistor to +5V
27-31 D11-D16 leave unconnected
32 -CE2 byte/word mode, connect to +5V
33 -VS1 leave unconnected
34 -IORD leave unconnected
35 -IOWR leave unconnected
36 -WE I/O write strobe, connect to PIC, PORTC[2]
37 READY CF card ready for new cmd, connect to PIC, PORTC[3]
38 VCC connect to +5V
39 -CSEL tie to ground
40 -VS2 leave unconnected
41 RESET leave unconnected
42 -WAIT CF busy, connect to PIC, PORTC[6]
43 -INPACK leave unconnected
44 -REG tie to +5V
45 BVD2 leave unconnected
46 BVD1 leave unconnected
47-49 D08-D10 leave unconnected
50 GND tie to ground
+
file: /Techref/member/rr-usa-p64/cf_inst.htm, 90KB, , updated: 2008/9/10 15:22, local time: 2024/8/31 17:31,
owner: RR-usa-P64,
3.133.155.235:LOG IN
|
©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://massmind.org/techref/member/rr-usa-p64/cf_inst.htm"> Data logger, Microchip PIC, FLASH file system, FAT 16, CF Card Reader, CF Card</A> |
Did you find what you needed? |