Author Topic: BURSTING YOUR 128: THE FASTLOAD BURST COMMAND  (Read 1692 times)

0 Members and 1 Guest are viewing this topic.

Offline Blacklord

  • Administrator
  • Forum god
  • *****
  • Posts: 3172
  • Country: au
  • Reputation: 779
  • Gender: Male
    • View Profile
« on: June 24, 2007, 04:20 PM »
(Reprinted from C= Hacking #3)

by Craig Bruce


This article discusses the well-unknown Fastload command of the 1571 and 1581
disk drive Burst Command Instruction Set.  If you look in the back of your '71
(or '81 I presume) disk drive manual, you will find that the information given
about the Fastload utility is not exactly abundant.

The Fastload command was intended to load program files into memory for
execution, but it can be used just as well for reading through sequential
files that would be much too large to load into a single bank of internal

To make use of the Fastload burst command, I implement a word counting utility
that will count the number of lines, words, and characters in a text file on a
1571 or 1581 disk drive.  The advantage of using the Fastload command over
regular sequential file accessing through the kernel and DOS is that the
Fastload operates about 3.5 times faster on both drives.


To use the word counting program, LOAD and RUN it like a regular BASIC
program.  It will ask you for the name of a file.  Enter the name if it is on
device number 8, or put a one character prefix and a ":" if it is on another
device.  A "b" means device 9, "c" device 10, etc.  The following are examples
of valid names:

. filename          "filename" on device 8
. b:filename        "filename" on device 9
. a:filename        "filename" on device 8

The file must be on either a 1571 or 1581 disk drive; the program will not
work with non-burst devices.  The program will work with either PRG or SEQ
files, since the Fastload command can be told not to worry about the file

I use the same definition of a word as the Unix "wc" command uses: a sequence
of characters delimited by whitespace, where whitespace is defined to be
SPACE, TAB, and NEWLINE (Carriage Return) characters.  To get the line count,
I simply count the number of NEWLINEs.  If the last line of the file does not
end with a NEWLINE character, then the count will be one line short.  This is
the same as the Unix wc command too.  A proper text file should have its last
line end with a NEWLINE character.

On my JiffyDOS-ified 1571 and 1581, I am able to achieve a word counting speed
of 5,400 chars/sec and 6,670 chars/sec, respectively.  I am not sure how much
of a difference JiffyDOS makes, but I am not willing to rip out the ROMs to
check.  I tested using a 318K file.


This section presents the burst reading library that you can incorporate into
your own programs and describes how the burst commands work.  The library has
three calls:

. burstOpen  ( .A=Device, .X=NameLen, burstBuf=Filename ) :
. burstRead  () : burstBuf, burstStatus, burstBufCount
. burstClose ()

I define three common storage variables for using this package: "burstBuf",
"burstStatus", and "burstBufCount".  "burstBuf" is a 256 byte area where the
data read in from the disk drive is stored before processing, and is located
at $0B00.  "burstStatus" is a zero-page location that keeps the status
returned from the burst command system.  This is needed by the user to detect
when the end of file has been encountered.  "burstBufCount" gives the number
of data bytes available in "burstBuf" after an open or read operation.  Its
value will be somewhere between 1 and 254.  A full sector contains 254 bytes
of data and two bytes of control information.

"burstStatus" and "burstBufCount" are defined to be at locations $FE and $FF,
respectively.  You are allowed to alter the values of the two variables and
the data buffer between calls, if you wish.  For reasons not completely
understood, interrupts must be disabled for the entire course of burst reading
a file.  I suspect this is because the IRQ service routine reads the interrupt
mask register of CIA#1, thus clearing the SerialDataReady flag that the burst
read routine waits for.  Anyway, the open routine does a SEI and the close
routine does a CLI, so you don't have to do this yourself.

If an error occurs during the exection of one of these routines, it will
return with the carry flag set and with the error code in the .A register
(same as the kernel (yes, I know that Commodore likes to call it the
"kernAl")).  Error codes 0 to 9 correspond to the standard kernel codes, error
code 10 means that the device is not a burst device, and error codes 16 to 31
correspond to the burst controller status codes 0-15.  If no error occurs, the
routines return with the carry flag clear, of course.

Only one file may be open at a time for Fastloading, since Fastload takes over
the disk drive and the entire serial bus.  Even regular files cannot be
accessed while a fastload is in progress.  Thus, Fastload is not suitable for
all file processing applications, but it works very well for reading a file
into memory (like for a text editor) and for summarization operations (like
word counting).  The burst library requires that the kernel and I/O space be
in context when it is called.


The way that a burst command is given is to give a magical incantation over
the command channel to the disk drive.  You can either use the low-level
serial bus calls (LISTN, SECND, CIOUT, and UNLSN) or use the OPEN and CHROUT
calls.  I used the low level calls for a little extra zip.

The burst command format for Fastload is given in the back of your drive

.  BYTE \ bit: 7     6     5     4     3     2     1     0  | Value
. -------+--------+-----+-----+-----+-----+-----+-----+-----+-------
.   0    |     0  |  1  |  0  |  1  |  0  |  1  |  0  |  1  |  "U"
.   1    |     0  |  0  |  1  |  1  |  0  |  0  |  0  |  0  |  "0"
.   2    |     P  |  X  |  X  |  1  |  1  |  1  |  1  |  1  |  159
. 3 - ?? |                                       |
. -------+--------------------------------------------------+-------

where "X" means "don't case" and "P" means "program".  If the P bit is '0'
then only program (PRG) files can be loaded, and if it is '1' then sequential
(SEQ) files can be loaded as well.  The package automatically sets this flag
for you.  Note that you don't have to do an Inquire Disk or Query Disk Format
in order to use this command like you have to do with the block reading and
writing commands.

If you want to try giving the incantation yourself, enter:


(where "FILENAME" is the name of some file that exists on your disk) on your
128 and your disk drive will spring to life and wait for you to read the file
data.  You can't read the data from BASIC, so to cancel the command:


The "burstOpen" call of this package accepts the name of the file to be loaded
at the start of the "burstBuf" buffer, the length of the filename in the .X
register, and the device number to load the file from in the .A register.  The
burst command header and the filename are sent to the disk drive as described

The open command also reads the first sector of the file, for two reasons.
First, the status byte returned for the first sector has special meaning.
Status code $02 means "file not found".  The package translates this into the
kernel error code.  Second, and most important, there is a bizarre feature
(read: "bug") in the Fastload command.  If the file to be read is only one
block long, then the number of bytes reported for the block length is two
bytes too short.  The following table gives the number of bytes reported and
the number of actual bytes in the sector:

. Actual   |    4    |    3    |    2    |    1    |    0    |
. ---------+---------+---------+---------+---------+---------+
. Reported |    2    |    1    |    0    |   255   |   255   |

This is where I ran into problems with Zed-128; the logic of my program
screwed up on a zero length.  I have corrected the problem here, though.  This
bug is bizarre because it only happens if the first sector is the only sector
in the file.  The reported length for all subsequent sectors is correct.  Note
also that 255 is reported for lengths of both 1 and 0.  This is because there
is no actual zero length for Commodore files.  If you OPEN1,8,2,"EMPTY" and
then immediately CLOSE1 you get a file with one carriage return character in

The open routine calls the read routine to read a sector and if it was the
only sector of the file, the two additional bytes are burst-read and put into
the data buffer.  Note that incrementing the reported count of 255 twice gives
the correct count of 1.  Also interesting in this case is that when the
1571/81 reports the 255, it actually transfers 255 bytes of data, all of which
are bogus.  It seems to me that they made the 1581 bug-compatible with the
1571 in this respect.

The open routine also executes a SEI for reasons discussed above.  The
information returned by the open call is the same as what is returned for the
"burstRead" call discussed next.


Once the Fastload command is started, the drive starts waiting to transfer the
data to you.  The transfer occurs sector by sector, with each sector preceeded
by a burst status byte.  The data is transferred using the shift register of
CIA#1 and the handshaking is done using the Slow Serial Clock line of CIA#2.
To receive a byte, you toggle the Slow Serial Clock line and wait for the
Shift Register Ready signal from CIA#1 and then read the data value from the
shift data register.

One of the clock registers in the CIA in the 1571/81 is used as the baud rate
generator for the serial line.  I think that it uses a delay of 4 microseconds
per bit, which gives a baud rate of 250,000 (31.25K/sec).  In my
experimentation, the maximum baud rate I have ever achieved in reality is
200,000 (25K/sec).  I read in my 1571 Internals book that the 250,000 baud
rate cannot actually be achieved because of electrical problems that I don't
understand.  This is an important difference because the data comes flying off
the surface of the disk at around 30K/sec and if the serial bus were fast
enough, it could be transferred to the computer as it is being read.  Some
things would be so much more convenient if whomever created the universe had
thought to make light go just a little bit faster.

The burst handshaking protocol slows the maximum transfer rate down to about
16K/sec.  Of course, the disk drive has more things to keep on top of than
just transferring data, so the actual burst throughput is lower than that:
about 5.4K/sec with my JiffyDOS-ified 1571 and about 7K/sec with a 1581.  Note
that you can probably increase your 1571's burst performance a bit by setting
it to use a sector interleave factor of 4, using the "U0>S"+CHR$(i) burst
command.  By default, a 1571 writes files with an interleave of 6.

All of the sectors before the last one will contain 254 bytes of data and the
last one will contain a specified number of bytes, from 1 to 254.  The status
code returned for the last sector is value $1F.  In this case, an additional
byte is sent before the data bytes that tells how many data bytes there will
be.  This is the value that is bugged for one-sector files as described in the
last section.  For those who like pictures, here are diagrams of the data
transferred for a sector:

.        REGULAR SECTOR                  LAST SECTOR OF FILE
.     +-------------------+             +--------------------+
.   0 | Burst Status Byte |           0 | Burst Status = $1F |
.     +-------------------+             +--------------------+
.   1 |                   |           1 |   Byte Count = N   |
. ... +  254 Data Bytes   |             +--------------------+
. 254 |                   |           2 |                    |
.     +-------------------+         ... |    N Data Bytes    |
.                                   N+1 |                    |
.                                       +--------------------+

If a sector returns a burst status code other than 0 (ok) or $1F (end), then
an error has occurred and the disk drive aborts the transfer, closes the burst
connection, and starts the drive light blinking.

The "burstRead" call of this package reads the data of the next sector of the
opened file into the "burstBuf" and returns the "burstStatus" and
"burstBufCount" (bytes read).  In the event of an error occuring, this routine
returns with the carry flag set and the translated burst error code in the .A
register (same as burstOpen).  When the last sector of the file has just been
read, this routine returns with a value of $1F in the "burstStatus" variable.


After reading the last data byte of the last sector of the file, the burst
connection and is closed automatically by the disk drive.  The "burstClose"
routine is not necessary for communication with the disk drive, but is
provided for completeness and to clear the interrupt disable bit (CLI) that
the open routine set to prevent interrupts while burst reading.


The following pseudo-code outlines how a user program is expected to use the
burst reading package:

.     jsr put_filename_into_burstBuf
.     ldx #filename_length
.     lda #device_number
.     jsr burstOpen
.     bcs reportError
. L1: jsr process_burstBuf_data
.     lda burstStatus
.     cmp #$1f
.     beq L2
.     jsr burstRead
.     bcc L1
.     jsr reportError
. L2: jsr burstClose


This section discusses the code that implements the word counting program.  It
is here in a special form; each code line is preceeded by a few special
characters and the line number.  The special characters are there to allow you
to easily extract the assembler code from the rest of this magazine (and all
of my ugly comments).  On a Unix system, all you have to do is execute the
following command line (substitute filenames as appropriate):

grep '^\.%...\!' Hack3 | sed 's/^.%...\!..//' | sed 's/.%...\!//' >wc.asm

Code: [Select]
.%001!  ;Word Count utility using the burst command set's Fastload facility
.%002!  ;written 92/06/25 by Craig Bruce for C= Hacking Net Magazine

The code is written for the Buddy assembler and here are a few setup

.%004!  .mem
.%005!  .bank 15
.%006!  .org $1c01
.%008!  ;*** BASIC startup code

This is what the "10 sys 7200" in BASIC looks like.  It is here so this
program can be executed with BASIC RUN command.

.%010!  .word $1c1c
.%011!  .word 10
.%012!  .byte $9e
.%013!  .asc  " 7200 : "
.%014!  .byte $8f
.%015!  .asc  " 6502 power!"
.%016!  .byte 0
.%017!  .word 0
.%018!  .word 0
.%020!  jmp main
.%022!  ;========== burst read library ==========
.%024!  burstStatus = $fe
.%025!  burstBufCount = $ff
.%026!  burstBuf = $b00

"serialFlag" is used to determine whether a device is Fast or not, and the
"ioStatus" (a.k.a. "ST") is to tell if a device is present or not.

.%027!  serialFlag = $a1c
.%028!  ioStatus = $90

"ciaIcr" is the interrupt control register of CIA#1.  It is polled to wait for
data becoming available in the shift register ("ciaData").  "ciaSerialClk" is
the Slow serial bus clock line that is used for handshaking on the Fast bus.

.%029!  ciaIcr = $dc0d
.%030!  ciaSerialClk = $dd00
.%031!  ciaData = $dc0c
.%033!  kernelListen = $ffb1
.%034!  kernelSecond = $ff93
.%035!  kernelCiout  = $ffa8
.%036!  kernelUnlsn  = $ffae
.%037!  kernelSpinp  = $ff47

This is the error code value this package returns if it detects that a device
is not Fast.

.%039!  errNotBurstDevice = 10
.%041!  burstFilenameLen = burstBufCount
.%043!  burstOpen = * ;(.A=Device, burstBuf=Filename, .X=NameLen):

Set up for a burst open: clear the Fast flag and the device not present flag.

.%044!     stx burstFilenameLen
.%045!     pha
.%046!     lda serialFlag
.%047!     and #%10111111
.%048!     sta serialFlag
.%049!     lda #0
.%050!     sta ioStatus
.%051!     pla

Command the disk device to Listen.  Then check if the device is present or not
(bit 7 of ioStatus).  If not present, return the kernel error code.

.%052!     jsr kernelListen
.%053!     bit ioStatus
.%054!     bpl +
.%056!     devNotPresent = *
.%057!     jsr kernelUnlsn
.%058!     lda #5
.%059!     sec
.%060!     rts

Tell disk device to listen on the command channel (channel #15).

.%062!  +  lda #$6f
.%063!     jsr kernelSecond

Send the "U0"+CHR$(159) burst command header.

.%064!     lda #"u"
.%065!     jsr kernelCiout
.%066!     bit ioStatus
.%067!     bmi devNotPresent
.%068!     lda #"0"
.%069!     jsr kernelCiout
.%070!     lda #$9f
.%071!     jsr kernelCiout

Send the filename.

.%072!     ldy #0
.%073!  -  lda burstBuf,y
.%074!     jsr kernelCiout
.%075!     iny
.%076!     cpy burstFilenameLen
.%077!     bcc -

Finish sending the burst command and make sure the device is Fast.

.%078!     jsr kernelUnlsn
.%079!     lda serialFlag
.%080!     and #$40
.%081!     bne +
.%082!     sec
.%083!     lda #errNotBurstDevice
.%084!     rts

Disable interrupts.

.%086!  +  sei

Prepare to receive data and signal the disk drive to start sending (by
toggling the slow serial Clock line).

.%087!     clc
.%088!     jsr kernelSpinp
.%089!     bit ciaIcr
.%090!     lda ciaSerialClk
.%091!     eor #$10
.%092!     sta ciaSerialClk

Read the first sector of the file.

.%093!     jsr burstRead

Check for errors.  Burst error code 2 (file not found) is translated to its
kernel equivalent.

.%094!     lda burstStatus
.%095!     cmp #2
.%096!     bcc +
.%097!     bne shortFile
.%098!     sec
.%099!     lda #4
.%100!  +  rts

Check if this is a one-block file.

.%102!     shortFile = *
.%103!     cmp #$1f
.%104!     bne openError
.%105!     ldy burstBufCount
.%106!     ldx #2

If so, we have to read the two bytes that the disk drive forgot to tell us
about.  For each byte, we wait for for the Shift Register Ready signal, toggle
the clock, and read the shift data register.  I can get away with reading the
data register after sending the acknowledge signal to the disk drive because I
am running with interrupts disabled and it could not possibly send the next
byte before I pick up the current one.  We wouldn't want any NMIs happening
while doing this, though.

.%108!     shortFileByte = *
.%109!     lda #$08
.%110!  -  bit ciaIcr
.%111!     beq -
.%112!     lda ciaSerialClk
.%113!     eor #$10
.%114!     sta ciaSerialClk
.%115!     lda ciaData
.%116!     sta burstBuf,y
.%117!     iny
.%118!     dex
.%119!     bne shortFileByte

Store the updated byte count and exit.

.%120!     sty burstBufCount
.%121!     clc
.%122!     rts

In the event of a burst error, re-enable the interrupts since the user might
not call the burstClose routine.  Return the translated error code.

.%124!     openError = *
.%125!     cli
.%126!     sec
.%127!     ora #$10
.%128!     rts

Read the next sector of the file.

.%130!  burstRead = * ;( ) : burstBuf, burstBufCount, burstStatus

Wait for the status byte to arrive.

.%131!     lda #8
.%132!  -  bit ciaIcr
.%133!     beq -

Toggle clock line for acknowledge.

.%134!     lda ciaSerialClk
.%135!     eor #$10
.%136!     sta ciaSerialClk

Get status byte and check.  If 2 or more and not $1F, then an error has
occurred.  If 0, then prepare to read 254 data bytes.

.%137!     lda ciaData
.%138!     sta burstStatus
.%139!     ldx #254
.%140!     cmp #2
.%141!     bcc actualRead
.%142!     cmp #$1f
.%143!     bne openError

If status byte is $1F, then get the next byte, which tells how many data bytes
are to follow.

.%144!     lda #8
.%145!  -  bit ciaIcr
.%146!     beq -
.%147!     ldx ciaData
.%148!     lda ciaSerialClk
.%149!     eor #$10
.%150!     sta ciaSerialClk
.%152!     actualRead = *
.%153!     stx burstBufCount
.%154!     ldy #0

Read the data bytes and put them into the burst buffer.  The clock line toggle
value is computed before receiving the data for a little extra zip.  I haven't
experimented with this, but you might be able to toggle the clock line before
receiving the data (however, probably not for the first byte).

.%156!     readByte = *
.%157!     lda ciaSerialClk
.%158!     eor #$10
.%159!     tax
.%160!     lda #8
.%161!  -  bit ciaIcr
.%162!     beq -
.%163!     stx ciaSerialClk
.%164!     lda ciaData
.%165!     sta burstBuf,y
.%166!     iny
.%167!     cpy burstBufCount
.%168!     bne readByte
.%169!  +  clc
.%170!     rts

Close the burst package: simply CLI.

.%172!  burstClose = *
.%173!     cli
.%174!     clc
.%175!     rts
.%177!  ;========== main program ==========

This is the word counting application code.

.%179!  bkWC = $0e
.%180!  bkSelect = $ff00
.%181!  kernelChrin  = $ffcf
.%182!  kernelChrout = $ffd2

The "wcInWord" is a boolean variable that tells whether the file scanner is
currently in a word or not.  The Lines, Words, and Bytes are 24-bit counters.

.%184!  wcInWord = 2 ;(1)
.%185!  wcLines = 3  ;(3)
.%186!  wcWords = 6  ;(3)
.%187!  wcBytes = 9  ;(3)
.%189!  main = *

Put the kernel ROM and I/O space into context then initialize the counting

.%190!     lda #bkWC
.%191!     sta bkSelect
.%192!     jsr wcInit

Follow the burst reading procedure outline.

.%193!     jsr wcGetFilename
.%194!     jsr burstOpen
.%195!     bcc +
.%196!     jsr reportError
.%197!     rts
.%198!  /  jsr wcScanBuffer
.%199!     lda burstStatus
.%200!     cmp #$1f
.%201!     beq +
.%202!     jsr burstRead
.%203!     bcc -
.%204!     jsr reportError
.%205!  +  jsr burstClose

Report the numbers of lines, words, and characters and then exit.

.%206!     jsr wcReport
.%207!     rts

Initialize the variables.

.%209!  wcInit = *
.%210!     lda #0
.%211!     ldx #8
.%212!  -  sta wcLines,x
.%213!     dex
.%214!     bpl -
.%215!     sta wcInWord
.%216!     rts

Get the device and filename from the user.  Returns parameters suitable for
passing to burstOpen.

.%218!  wcGetFilename = * ;() : burstBuf=Filename, .A=Device, .X=FilenameLen

Display the prompt.

.%219!     ldx #0
.%220!  -  lda promptMsg,x
.%221!     beq +
.%222!     jsr kernelChrout
.%223!     inx
.%224!     bne -

Get the input line from the user.

.%225!  +  ldx #0
.%226!  -  jsr kernelChrin
.%227!     sta burstBuf,x
.%228!     cmp #13
.%229!     beq +
.%230!     inx
.%231!     bne -
.%232!  +  jsr kernelChrout

Extract the device number from the start of the input line.  If it is not
there, assume device number 8.

.%233!     lda #8
.%234!     cpx #2
.%235!     bcc filenameExit
.%236!     ldy burstBuf+1
.%237!     cpy #":"
.%238!     bne filenameExit
.%239!     sec
.%240!     lda burstBuf
.%241!     sbc #"a"-8
.%242!     tay

If a device name was present, then we have to move the rest of the filename
back over it now that we've extracted it.

.%243!     ldx #0
.%244!  -  lda burstBuf+2,x
.%245!     sta burstBuf,x
.%246!     cmp #13
.%247!     beq +
.%248!     inx
.%249!     bne -
.%250!  +  tya
.%251!     filenameExit = *
.%252!     rts
.%254!     promptMsg = *
.%255!     .asc "enter filename in form filename, or a:filename, "
.%256!     .asc "or b:filename, ..."
.%257!     .byte 13
.%258!     .asc "where 'a' is for device 8, 'b' is for device 9, ..."
.%259!     .byte 13,0

Scan the burst buffer after reading a sector into it.

.%261!  wcScanBuffer = *
.%262!     ldy #0
.%263!     cpy burstBufCount
.%264!     bne +
.%265!     rts
.%266!  +  ldx wcInWord
.%267!  -  lda burstBuf,y
.%268!  ;   jsr kernelChrout  ;uncomment this line to echo the data read
.%269!     cmp #13
.%270!     bne +

If the current character is a carriage return, then increment the line count.

.%271!     inc wcLines
.%272!     bne +
.%273!     inc wcLines+1
.%274!     bne +
.%275!     inc wcLines+2

If the character is a TAB, SPACE, or a RETURN, then it is a Delimiter;
otherwise, it is considered a Letter.

.%276!  +  cmp #33
.%277!     bcs isLetter
.%278!     cmp #" "
.%279!     beq isDelimiter
.%280!     cmp #13
.%281!     beq isDelimiter
.%282!     cmp #9
.%283!     beq isDelimiter
.%285!     isLetter = *

If the character is a Letter and the previous one was a Delimiter, then
increment the word count.

.%286!     cpx #1
.%287!     beq scanCont
.%288!     ldx #1
.%289!     inc wcWords
.%290!     bne scanCont
.%291!     inc wcWords+1
.%292!     bne scanCont
.%293!     inc wcWords+2
.%294!     jmp scanCont
.%296!     isDelimiter = *
.%297!     ldx #0
.%299!     scanCont = *
.%300!     iny
.%301!     cpy burstBufCount
.%302!     bcc -

Add the number of bytes in the burst buffer to the total byte count for the

.%303!     clc
.%304!     lda wcBytes
.%305!     adc burstBufCount
.%306!     sta wcBytes
.%307!     bcc +
.%308!     inc wcBytes+1
.%309!     bne +
.%310!     inc wcBytes+2
.%311!  +  stx wcInWord
.%312!     rts

Report the number of lines, words, and bytes read.  Uses a "printf" type of

.%314!  wcReport = *
.%315!     ldx #0
.%316!  -  lda reportMsg,x
.%317!     beq reportExit
.%318!     cmp #13
.%319!     bcs +
.%320!     stx 14
.%321!     tax
.%322!     lda 2,x
.%323!     sta 15
.%324!     lda 0,x
.%325!     ldy 1,x
.%326!     ldx 15
.%327!     jsr putnum
.%328!     ldx 14
.%329!     jmp reportCont
.%330!  +  jsr kernelChrout
.%331!     reportCont = *
.%332!     inx
.%333!     bne -
.%334!     reportExit = *
.%335!     rts
.%337!     reportMsg = *
.%338!     .byte 13
.%339!     .asc "lines="
.%340!     .byte wcLines
.%341!     .asc ", words="
.%342!     .byte wcWords
.%343!     .asc ", chars="
.%344!     .byte wcBytes,27
.%345!     .asc "q"
.%346!     .byte 13,0

Reports the error number given in the .A register.  Called after an error is
returned from a burst routine.

.%348!  reportError = * ;( .A=errNum )
.%349!     pha
.%350!     ldx #0
.%351!  -  lda errorMsg,x
.%352!     beq +
.%353!     jsr kernelChrout
.%354!     inx
.%355!     bne -
.%356!  +  pla
.%357!     ldy #0
.%358!     ldx #0
.%359!     jsr putnum
.%360!     lda #13
.%361!     jsr kernelChrout
.%362!     rts
.%364!     errorMsg = *
.%365!     .asc "*** i/o error #"
.%366!     .byte 0
.%368!  ;==========library==========

Routine to print out the 24-bit number given in .AYX.

.%370!  libwork = $60
.%371!  itoaBin = libwork
.%372!  itoaBcd = libwork+3
.%373!  itoaFlag = libwork+7
.%375!  putnum = *

Initialize binary and BCD (Binary Coded Decimal) representations of number.

.%376!     sta itoaBin+0
.%377!     sty itoaBin+1
.%378!     stx itoaBin+2
.%379!     ldx #3
.%380!     lda #0
.%381!  -  sta itoaBcd,x
.%382!     dex
.%383!     bpl -
.%384!     sta itoaFlag
.%385!     ldy #24
.%386!     sed

Rotate each bit out of the binary number and then multiply the BCD number by
two and add the bit in.  Effectively, we are shifting the bits out of the
binary number and into the BCD representation of the number.

.%388!     itoaNextBit = *
.%389!     asl itoaBin+0
.%390!     rol itoaBin+1
.%391!     rol itoaBin+2
.%392!     ldx #3
.%393!  -  lda itoaBcd,x
.%394!     adc itoaBcd,x
.%395!     sta itoaBcd,x
.%396!     dex
.%397!     bpl -
.%398!     dey
.%399!     bne itoaNextBit
.%400!     cld

Take the BCD bytes and spit out the two digits they contain.

.%401!     ldx #0
.%402!     ldy #0
.%403!  -  lda itoaBcd,x
.%404!     jsr itoaPutHex
.%405!     inx
.%406!     cpx #4
.%407!     bcc -
.%408!     rts
.%410!     itoaPutHex = *
.%411!     pha
.%412!     lsr
.%413!     lsr
.%414!     lsr
.%415!     lsr
.%416!     jsr itoaPutDigit
.%417!     pla
.%418!     and #$0f

Print out the individual digits of the number.  If the current digit is zero
and all digits so far have been zero, then don't output anything, unless it is
the last digit of the number.

.%420!     itoaPutDigit = *
.%421!     cmp itoaFlag
.%422!     bne +
.%423!     cpy #7
.%424!     bcc itoaPutDigitExit
.%425!  +  ora #$30
.%426!     sta itoaFlag
.%427!     jsr kernelChrout
.%428!     itoaPutDigitExit = *
.%429!     iny
.%430!     rts


Here is the binary executable in uuencoded form.  The CRC32 of it is
3676144922.  LOAD and RUN it like a regular BASIC program.

Code: [Select]
begin 640 wc


[1] Commodore Business Machines, _Commodore_1571_Disk_Drive_User's_Guide_,
    CBM, 1985.

[2] Rainer Ellinger, _1571_Internals_, Abacus Software, June 1986.

Offline Hydrophilic

  • 128D user
  • *******
  • Posts: 1430
  • Reputation: 233
  • Gender: Male
    • View Profile
    • H2Obsesson
« Reply #1 on: January 13, 2012, 02:57 AM »
Sorry for bumping an old thread, but this seemed like a good place for it.  I think Craig Bruce's article (above) is pretty good, and utilizing the native power of the C128 (fast-serial) is a good thing.  I own and have read (several times) the references he mentioned (and several others), but the fast-serial protocol has never been fully explained, as far as I know.
So here is my attempt to help rectify the situation.  First, if you haven't already, take a look at the 1571 or 1581 user's manual and you will find only a minimal description of the fast-serial protocol.  (Not to be confused with the many fast-serial routines that are listed.)  This is really sad, because if you look in the C64 Programmer's Reference Guide, they provide a very detailed description of the slow serial bus protocol.  Unfortunately the C128 Programmer's Reference Guide does not provide a good description of either!
So, I drew up this diagram to hopefully educate and inspire C128 programers.  This shows how the C128 establishes fast-serial protocol with devices.  It does NOT show how individual commands (like burst-load or burst-read) work... I think the documentation of the 15x1 manuals is sufficient.
Anyway, here is a run-down of the info depicted in the attached image:
  • The DATA line is released (presumably it will go to +5V).
  • The C128 sends $FF on the fast-serial line which keeps DATA at +5V but toggles SRQ 8 times.
  • The C128 waits 128 microseconds.
  • The Command begins by pulling ATN and CLK to 0V.
  • The C128 waits 1000 microseconds (slightly more if the VIC-II screen is not blanked).
  • The device (C1541, C1571, etc.) should pull DATA to 0V
  • The C128 checks DATA and reports DEVICE NOT PRESENT if it has not been pulled down to 0V.
  • The C128 releases CLK (it should go to +5V).
  • The C128 waits for DATA to be released
  • The device may send Device Request Fast (DRF) signal
  • Once DATA is released (C128 will wait forever), it checks the CIA to see if the device supports fast-serial
  • The C128 sends the command using slow-serial protocol (even if device supports fast-serial)
  • The C128 waits a moment for the device to acknowledge command by pulling DATA down to 0V.
  • The C128 may release ATN (if the command is complete)

There are 3 important things to note about fast-serial that differ from slow serial:
  • The DATA line is inverted (a 0 bit = +5V for example) for fast data
  • The DATA bits are sent high-bit-first (big endian)
  • The HRF is sent before ATN is pulled low (slow serial never sends HRF)
By the way, HRF = Host Request Fast, a special signal sent by C128 to request fast-serial communication.  The device MAY respond with a DRF = Device Request Fast AFTER the C128 sends the device number.  So in the above diagram, if the C128 is sending the first command byte (the device#) the device will NOT send DRF.  But, when the C128 sends the secondary address of talk/listen, then the device may send a DRF.
Let me know if any of that is confusing and I will try to clarify.
After sleeping on it, I realized a couple of issues with the diagram.  First, as explained above, the 1571/81 does not send DRF when the C128 sends the first command byte, so the diagram is misleading.  More important, as far as I can tell, the 1571/81 never sends DRF when Attention is active, but after all command bytes have been sent and before the first data byte is transferred.  The discription above implies that DRF is sent during the second command byte.  Well I guess that would work, but as far as I can tell it is not how the 1571/81 operate.
The second thing I realized is the time interval shown for the C128's HRF is labeled as 64 microseconds.  Well, that is time period of a fast-serial byte transferred by the 1571/81, but the C128 for some reason seems uses a slower rate.  In other words, the DRF is 64 microseconds, but the HRF is actually 80 microseconds.
I guess I need to correct that diagram and make a new one showing a data (as opposed to command) fast-serial transfer.  But thought I should tell you now...
« Last Edit: January 14, 2012, 10:15 AM by Hydrophilic »
I'm kupo for kupo nuts!

Offline Hydrophilic

  • 128D user
  • *******
  • Posts: 1430
  • Reputation: 233
  • Gender: Male
    • View Profile
    • H2Obsesson
« Reply #2 on: January 14, 2012, 01:42 PM »
I've attached an updated image that should help clarify things.  It actually has two parts.
In the first part, the C128 sends a command byte where the following happens:
  • The C128 sends a dummy fast-serial byte (which leaves DATA high) to signal Host Request Fast (HRF)
  • The C128 waits approximately 128 microseconds
  • The C128 pulls Attention (ATN) and Clock (CLK) low
  • The C128 waits approximately 1000 microseconds and then tests DATA is low
  • The C128 releases CLK (it should go high) and waits (possible forever) for DATA to high
  • The device(s) releases DATA when it (they) is (are) ready to receive
  • The C128 pulls CLK low then sends the command using slow-serial protocol
  • The C128 waits a while for the device to pull DATA low to acknowledge receipt
In the second part, the C128 transmits two data bytes where the following occurs:
  • The C128 releases CLK to indicate it is ready to transmit (first byte)
  • The device sends a dummy fast-serial byte (which leaves DATA low) to signal Device Request Fast (DRF)
  • The device releases DATA to indicate ready to receive
  • The C128 pulls CLK low to signal start of transmission
  • The C128 sends the data using fast-serial hardware
  • The device pulls DATA low to acknowledge receipt
  • The C128 releases CLK to indicate it is ready to transmit (second byte)
  • The device releases DATA to indicate ready to receive
  • The C128 pulls CLK low to signal start of transmission
  • The C128 sends the data using fast-serial hardware
  • The device pulls DATA low to acknowledge receipt
In the second diagram (data sent using fast-serial), you should note the two bytes are transmitted differently.  The C1571/81 only sends a DRF before the first byte but not for any remaining bytes.  This is an effecient method, but it is not strictly required.  For example, the device could receive 7 data bytes using the slow-serial protcol, then send DRF and receive the remaining bytes using fast-serial.  As another example, the device could also send DRF for every byte.  Both alternate examples are silly because they needlessly waste time.
I hope that makes sense, if not let me know.  Now I need to make a diagram for fast-serial receiption and burst transfer.  Note burst transfer is similar but different than fast-serial byte transfer described here.
I'm kupo for kupo nuts!

Offline jsixis

  • KIM-1 user
  • **
  • Posts: 12
  • Reputation: 0
    • View Profile
« Reply #3 on: June 12, 2012, 07:27 AM »
 Excellent, this should be a sticky.