📄 fat32.s
字号:
; Note that temp2 still contains zero from the previous calculations
ldi ZL,lo8(STREAMFILE_SEC_REM)
ldi ZH,hi8(STREAMFILE_SEC_REM) ; Z points to bytes remaining in sect (16 bits)
ld temp,Z ; temp contains low byte of BytesRemaining
sub temp,r21 ; subract NumBytesRead from lowbyte
st Z,temp ; store low byte back
ldd temp,Z+1 ; temp contains next byte of BytesRemaining
sbc temp,temp2 ; subtract the carry flag from it
std Z+1,temp ; and store the result back
; If BytesRemainingInSector != 0 then Return
ld temp,Z ; get low byte of BytesRemaining
cpi temp,0
brne StreamFile_RD_9 ; branch forward if low byte != 0
ldd temp,Z+1 ; else get high byte of BytesRemaining
cpi temp,0
breq StreamFile_RD_10 ; branch forward if high byte == 0
StreamFile_RD_9:
ret ; return if BytesRemaining (16 bits) != 0
; execute here if BytesRemainingInSector (16 bits) == 0
StreamFile_RD_10:
; Decrement SectorsRemainingInCluster (STREAMFILE_SECCLUS_REM)
; If SectorsRemainingInCluster == 0 then
; Change state variable to LearnNextCluster
; Jump to LearnNextCluster state
ldi ZL,lo8(STREAMFILE_SECCLUS_REM)
ldi ZH,hi8(STREAMFILE_SECCLUS_REM)
ld temp,Z ; temp contains STREAMFILE_SECCLUS_REM
dec temp ; decrement it
st Z,temp ; and store the result
brne StreamFile_RD_11 ; branch forward if it's NOT zero
; execute here if SectorsRemainingInCluster == 0
ldi ZL,lo8(STREAMFILE_ST)
ldi ZH,hi8(STREAMFILE_ST) ; point Z to StreamFile state variable
ldi temp,StreamFile_St_LearnClus
st Z,temp ; store LearnNextCluster state
jmp StreamFile_LC ; jump to LearnNextCluster state
; execute here if SectorsRemainingInCluster != 0
StreamFile_RD_11:
; Change state variable to WaitDriveNotBusy
; Jump to WaitDriveNotBusy state
ldi ZL,lo8(STREAMFILE_ST)
ldi ZH,hi8(STREAMFILE_ST) ; point Z to StreamFile state variable
ldi temp,StreamFile_St_WaitDrv
st Z,temp ; store WaitDriveNotBusy state
rjmp StreamFile_WD ; jump to WaitDriveNotBusy state
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; WaitDriveNotBusy state
StreamFile_WD:
; Read drive status register
; If drive busy (BSY flag set) then return
ldi param1,IDERD_ST
rcall IDE_read8 ; read drive status register
sbrc param1,IDEST_BSY_BIT ; skip forward if BSY bit clear
ret ; BSY flag set so return
; If drive not ready (RDY flag clear) then return
sbrs param1,IDEST_RDY_BIT ; skip forward if RDY bit set
ret ; return if RDY flag clear - drive not ready yet
; Change state variable to ReadData
; Store 512 in STREAMFILE_SEC_REM (# of bytes remaining in this sector)
; Return
ldi ZL,lo8(STREAMFILE_ST)
ldi ZH,hi8(STREAMFILE_ST) ; point Z to StreamFile state variable
ldi temp,StreamFile_St_ReadData
st Z,temp ; state variable is ReadData
ldi ZL,lo8(STREAMFILE_SEC_REM)
ldi ZH,hi8(STREAMFILE_SEC_REM) ; Z now points to STREAMFILE_SEC_REM
ldi temp,lo8(512)
st Z+,temp ; store low byte of 512
ldi temp,hi8(512)
st Z,temp ; store high byte of 512 into STREAMFILE_SEC_REM
ret ; and return
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; LearnNextCluster state
StreamFile_LC:
; Get current cluster number (STREAMFILE_CLUS) (32 bits)
; Push it onto stack
ldi ZL,lo8(STREAMFILE_CLUS)
ldi ZH,hi8(STREAMFILE_CLUS)
ldd temp,Z+3 ; get high byte of current cluster number
push temp ; push high byte onto stack
ldd temp,Z+2
push temp
ldd temp,Z+1
push temp
ld temp,Z
push temp ; push low byte of cluster number onto stack
; Call CalcNextCluster
; If result code == Busy then return
rcall CalcNextCluster
pop r0 ; low byte of potential result
pop r1
pop r2
pop r3 ; high byte of potential result
cpi param1,CalNxCl_Busy
brne StreamFile_LC_1 ; branch forward if CalcNextCluster not busy
ret ; otherwise return
; execute here if CalcNextCluster not busy anymore
StreamFile_LC_1:
; If result code == ChainEnd then
; Write "stop" into command variable
; Change state variable to Idle state & return
cpi param1,CalNxCl_ChainEnd
brne StreamFile_LC_2 ; branch forward if result != ChainEnd
; execute here if CalcNextCluster returned ChainEnd
ldi ZL,lo8(STREAMFILE_CMD)
ldi ZH,hi8(STREAMFILE_CMD) ; Z points to command variable
ldi temp,StreamFile_Cmd_Stop
st Z,temp ; write Stop into command variable
ldi ZL,lo8(STREAMFILE_ST)
ldi ZH,hi8(STREAMFILE_ST) ; point Z to StreamFile state variable
ldi temp,StreamFile_St_Idle
st Z,temp ; store Idle state
ret ; and return
; execute here if result code != busy or Chainend, ie, result code is "done" (by default)
StreamFile_LC_2:
; Get new cluster number off stack & write into STREAMFILE_CLUS
; note that we cunningly already have resulting cluster number in r3:r0
ldi ZL,lo8(STREAMFILE_CLUS)
ldi ZH,hi8(STREAMFILE_CLUS)
st Z,r0 ; store low byte of new cluster number
std Z+1,r1
std Z+2,r2
std Z+3,r3 ; store high byte of new cluster number
; Change state varible to SeekCluster
; Jump to SeekCluster state
ldi ZL,lo8(STREAMFILE_ST)
ldi ZH,hi8(STREAMFILE_ST) ; point Z to StreamFile state variable
ldi temp,StreamFile_St_SeekClus
st Z,temp ; store SeekCluster state
jmp StreamFile_SC ; jump to SeekCluster state
;**********************************************************************************
;*
;* CalcNextCluster
;*
;* This routine is the one that knows about the FAT32 file allocation table
;* structure. Given a cluster number, it looks in the FAT and determines what
;* the next cluster number for that file is.
;*
;* This routine functions as a state machine. This is because this routine
;* has to seek the drive to a particular sector (which it calculates based on
;* the requested cluster number) and it has to wait until the drive has
;* performed the seek.
;*
;* This routine must be called repeatedly. While it's waiting for the drive
;* it will just return "busy" to the calling function. Once it has managed to
;* get the next cluster number it returns "done" to the calling function, and
;* returns the cluster number. It returns "busy" or "done" in param1. It returns
;* the next cluster number on the stack (!), by replacing the passed-in cluster
;* number with the new cluster number. So that when the calling function does
;* its 4 pops to clean up the stack, it gets the new cluster number at the same time.
;*
;* If there is no next cluster number, ie, it's hit the end of the cluster chain,
;* it returns "ChainEnd".
;*
;* The most complex part of this routine is calculating where in the drive the
;* FAT entry we need is. The FAT structure is simple: each cluster has 4 bytes
;* in the table, starting with cluster number 0. The start of the table is in
;* found in the FAT_START_SEC 16-bit variable. There are 512 bytes in each
;* sector, which corresponds to (512/4) 128 clusters.
;* Hence, the sector number containing our cluster entry is
;* equal to: (cluster/128) + FAT_START_SEC
;* The offset into that sector will be: REMAINDER( cluster/128 ) x 4
;*
;* Dividing by 128 means shifting right 7 bits. This means losing the low 7 bits.
;* Determining remainder( cluster/128 ) is to simply take the low 7 bits of cluster.
;* Multiplying by 4 means shift left 2 bits (inserting zeros at the LSB of course).
;*
;* Although there are 4 bytes per cluster entry, each read of the drive
;* returns 2 bytes. So, in terms of number of drive reads, the offset into the
;* sector is only remainder(cluster/128) x 2. This is an 8-bit number.
;*
;* Just out of interest:
;* This relatively simple system only works because the FAT is a series of
;* consecutive sectors. Unlike directories and files, where clusters may be scattered
;* around the drive, resulting in the need to follow the cluster chains.
;*
;*
;* The following added Dec 21 2001 (and later)
;* As described above this routine has to seek the drive back to the FAT every time.
;* This is both very hard on the drive and it's slow. It would be nice to keep an
;* image of the FAT in SRAM; unfortunately this is not possible. So we'll do the
;* next best thing. Unfragmented files have consectutive cluster numbers. So when
;* it does a seek to a sector this routine can "look ahead" and determine if the next
;* few clusters are indeed consectutive. If they are it can hold this information
;* in a couple of variables, so that if it's asked about one of those clusters
;* (which it likely will be) it already knows the answer is "the next cluster" and
;* can respond accordingly, without having to reread the FAT. That's the concept.
;* In practice: the 32-bit number CALNXCL_CLUS_LO holds the low cluster number, and
;* CALNXCL_CLUS_HI holds the high cluster number, for the series of clusters we know
;* (from looking ahead in the FAT) that are consecutive. Important: for this series
;* of clusters, _including_ the lo and hi numbers, we know that the next cluster
;* number is the current cluster number +1. If the next cluster in the FAT is not
;* consecutive (ie fragmented file) then we'll detect that and make the hi cluster
;* number zero. For example, in the FAT (decimal numbers shown):
;*
;* Current cluster # Next cluster # (this column is the actual FAT entries)
;* 17 18
;* 18 19
;* -> 19 20
;* 20 21
;* 21 22
;* 22 23
;* 23 24
;* 24 39
;* 40
;*
;* Let's say we're called asking what the next cluster number is for a current
;* cluster 19. We look in the FAT and see the answer is 20. This is a consecutive
;* number, ie the file is not fragmented here, so we continue on. (If the answer was
;* not 20, ie the file was fragmented, we would zero the hi variable and just return
;* 20. But for this example it's not fragmented here...) Because it's consecutive
;* we write 20 into the lo variable and look at the next number, in this case 21, which
;* is again consecutive. If it was not we would zero the hi variable and return. But it's
;* OK so we look again and see 22, then 23, then 24, then 39 (!). So either the file is
;* fragmented or it's finished; this routine has no way of knowing. But the variables
;* would contain: CALNXCL_CLUS_LO = 20 and CALNXCL_CLUS_HI = 23 for this example.
;* It will only read the FAT until either it finds a fragmentation OR it reaches the
;* end of the current sector. Seeing as CALNXCL_OF contains the number of drive reads
;* required to reach the current cluster number, and then at that point it will perform
;* 2 reads to get the next cluster number, the number of drive reads this code section
;* can perform from the current sector is 256 - (CALNXCL_OF + 2), ie 254 - CALNXCL_OF.
;*
;*
;* This routine functions as follows:
;*
;* SeekDrive state (CALNXCL_ST = CalNxCl_SDS)
;* If SeekSector is idle then
;* change state to SeekDrive2 & return with busy
;* else
;* force SeekSector idle & return with busy
;*
;* SeekDrive2 state (CALNXCL_ST = CalNxCl_SDS2)
;* Pull current cluster number off stack (32 bit value)
;* IF current_cluster >= CALNXCL_CLUS_LO AND current_cluster <= CALNXCL_CLUS_HI
;* THEN return current_cluster + 1 (via the stack)
;* Calculate offset (drive reads) into sector = remainder(cluster/128) x 2
;* Write result into CALNXCL_OF (8 bit value)
;* Calculate FAT sector number = (Cluster/128) + FAT_START_SEC
;* Command drive to seek to that sector
;* Store that sector number (for use by the next state)
;* Change state variable to GetCluster
;* Write CalNxCl_Busy into param1
;* Return
;*
;* GetCluster state (CALNXCL_ST = CalBxCl_GCS)
;* If drive not seeked yet then return with CalNxCl_Busy in param1
;* Perform CALNXCL_OF reads of the drive (and throw away the data)
;* Read the next 4 bytes (2 16-bit drive reads) - this is cluster number
;* If cluster number == $0FFFFFFF then return with CalNxCl_ChainEnd in param1 & revert to SeekDrive state
;* Write cluster number onto stack, replacing passed-in cluster number
;* (the following indented lines added to implement the FAT pseudo-caching)
;* Store this new cluster number in CALNXCL_CLUS_LO
;* read_counter = 254 - CALNXCL_OF
;* if read_counter == 0 then zero CALNXCL_CLUS_HI and hop forward (out of indented code)
;* next_cluster_temp = CALNXCL_CLUS_LO + 1 (a prediction of what we want the next cluster to be)
;* Read the next 4 bytes (2 16-bit drive reads) - this is the next cluster number
;* if this next cluster number != next_cluster_temp then zero CALNXCL_CLUS_HI and hop forward (out of indented code)
;* CALNXCL_CLUS_HI = next_cluster_temp - 1
;* decrement read_counter by 2
;* WHILE read_counter != 0 do {
;* next_cluster_temp += 1
;* Read the next 4 bytes (2 16-bit drive reads) - this is the next cluster number
;* if this next cluster number != next_cluster_temp then break out of the while loop
;* CALNXCL_CLUS_HI = next_cluster_temp - 1
;* decrement read_counter by 2
;* }
;* Change state variable to SeekDrive
;* Write CalNxCl_Done into param1
;* Return
;*
;*
;* All values are read from the drive low-byte first.
;*
;*
;* Accepts: cluster number on the stack (32 bit value)
;* Returns: result code in param1; next cluster number on the stack
;* Uses: temp, temp2, param1, param2, Z, r0, r1, r2, r3, flags.
;*
;*
;**********************************************************************************
CalcNextCluster:
; Jump to the correct state (this is the state dispatcher code)
ldi ZL,lo8(CALNXCL_ST)
ldi ZH,hi8(CALNXCL_ST) ; point Z to CalcNextCluster state variable
ld temp,Z ; get the state
cpi temp,CalNxCl_SDS
breq CalcNextCluster_SDS ; branch if state is SeekDrive
cpi temp,CalNxCl_SDS2
breq CalcNextCluster_SDS2 ; branch if state is SeekDrive2
jmp CalcNextCluster_GCS ; otherwise jump to GetCluster state
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -