8

Since asking a similar question, which is really exclusively about using KERNAL to transfer between disk drive and computer, comments, answers and others hint at the fact that using the KERNAL really is not a good way to achieve what I'm after. This gave rise to a meta discussion, the upshot of which is that we're better off with a separate Q&A to detail the other options.

My requirements is that I need to have a usable amount of RAM available to me on the floppy drive so I can run a program on it. I should like to leave some time spare on the C64 also, because if the host computer spends the whole time transferring data, that would defeat the point ☺. In my case, I should need to transfer around 40 bytes per frame in the hostward direction, and a handful of bytes in the other direction per frame.

  • One problem is that you may run into is that the faster you transfer data, the more CPU time is spent on both ends bitbanging the data across the serial bus. At some point you'd lose any advantage of using the 1541 CPU as a coprocessor. – Ross Ridge Mar 9 at 20:34
  • Burst mode to a burst capable device, like C1571, may be more realistic option. – Brian H Mar 9 at 20:37
  • What's a usable amount of RAM? You're starting with only 2kb if memory serves. Otherwise, key observation: the 1541 and the C64 are very close to the same clock speed, so you should seriously consider using both the data and clock lines as data, if you can persuade yourself that the inevitable drift won't cause a fault within 40 bytes. – Tommy Mar 9 at 20:38
  • 1
    This is an extrem broad question - unless you narrow the requirements down a bit more - like what interface is to be used, what kind of messages and basic protocol requirements. -- In general I could think of a host clocked transfer, much like Supercat suggested, leaving the path set by Commodore. With fixed formats and sequences, using ATN and SRQ for protocol handling. – Raffzahn Mar 9 at 20:41
  • 1
    A 1581 has 8 KB of RAM. – Tim Locke Mar 9 at 21:29
4

SJLoad is a JiffyDos-compatible fast loader that goes faster by disabling the display; its per-byte loop is:

ltransferbyte:
    nop     ; timing critical section
    nop
    nop
    nop
    lda #$03
    ldx #$23
    stx $dd00   ; data=active,clock=inactive,ATN=inactive
    bit $dd00
    bvc lloadinnerloop  ; branch if 1541 sets clock active (needs to load next block)
    nop
    sta $dd00   ; set data inactive
    lda $dd00   ; read bits 1/0
    nop
    lsr
    lsr
    eor $dd00   ; read bits 3/2
    bit $00     ; burn cycles
    lsr
    lsr
    eor $dd00   ; read bits 5/4
    bit $00     ; burn cycles
    lsr
    lsr
    eor $dd00   ; read bits 7/6
    eor #$03
    sta ($ae),y ; store byte
    inc $ae     ; load address lo
    bne ltransferbyte
    inc $af     ; load address hi
    jmp ltransferbyte

I count that as:

  • nop, nop, nop, nop: 2+2+2+2 = 8
  • lda #, ldx#: 2+2 = 4
  • stx abs, bit abs: 4+4 = 8
  • bvc, untaken: 2
  • nop, sta abs, lda abs: 2+4+4 = 10
  • nop, lsr, lsr: 2+2+2 = 6
  • eor abs, bit zp, lsr, lsr: 4+3+2+2 = 11
  • eor abs, bit zp, lsr, lsr: 4+3+2+2 = 11
  • eor abs, eor imm: 4+2 = 6
  • sta (ind), y: 6
  • inc zp: 5
  • bne, taken 255/256 of the time: 3 * (255/256) + 1 * (1/256) = 770/256
  • inc zp, occurs 1/256 of the time: 5 * (1/256) = 5/256
  • jmp abs, occurs 1/256 of the time: 3 * (1/256) = 3/256

= an average of 80 and 10/256 cycles per byte.

So to transfer 40 bytes we're talking 3202 cycles, which is about 50.8 lines on the worst case of machines with a 63-cycle line length. That fits into the vertical border, even with setup costs, and appears to write fully-decoded bytes so there's no additional buffer processing. Supposing you lose 55 lines transferring bytes with a naive 'add ~10%' back-of-an-envelope guess and you're still looking at almost 80% of a frame being free for other tasks even on an NTSC machine.

So my advice is: crib what you can from JiffyDOS as far as a transfer protocol goes, as it seems to match at least your performance requirements.

| improve this answer | |
  • As far as I understand it the question is about data transfer with display not only enabled, but updated by the data received as well. – Raffzahn Mar 9 at 21:52
  • Good answer. Installing standard JiffyDOS into the C64 and drive would probably be the first thing I'd try. If both drive and host lose max. 25% to the transfer overhead, and the host can do it without being affected by any interrupts or VIC-II bad lines, then seems pretty predictable to work within those constraints for the rest of the 75% per frame available on both CPUs. Plus you have VBLANK time. Trouble is figuring out how to have user code coexist nicely with JiffyDOS code, which I'm sure uses some of the 2kB in the 1541. Who knows? It might even suck up the whole zero page. – Brian H Mar 9 at 22:52
  • @Raffzahn right; I started with code that is designed for the display off to see how fast you can go if you don't have to worry about bad lines and sprite fetches, then calculated it out for 40 bytes to see whether it would fit in the vertical area where there are no sprites or bad lines. And I think it fits, though I showed my arithmetic in case I've made a calculation or logical error. – Tommy Mar 10 at 2:21
2

Here are some benchmarks:

  • RapiDOS Pro uses a special cable to achieve 13 KB/s
  • Dolphin DOS achieves 8 KB/s
  • JiffyDOS achieves 5.1 KB/s
  • RapiDOS (non-Pro) achieves 3.4 KB/s

If you expand your question to a C128 with a 1571:

  • A C128 w/1571 with JiffyDOS achieves 5.7 KB/s
  • A C128 w/1571 in Burst Mode achieves 5.2 KB/s
| improve this answer | |
2

Without changing the hardware (via rewiring the IEC connector in the C64 or using a different connector instead of or in addition to the IEC connector) you have three basic options.

Kernal Routines

The KERNAL routines for communicating across the IEC bus (TALK, TKSA, ACPTR, etc.) are relatively slow for several reasons. First, the routines themselves are not the most efficient. This is not just code in the routines themselves that's not as optimized as it could be, but also that you usually have to call several different routines to do a full bus transaction for an outgoing request and incoming data and they also do their bus transactions in a way that's not always the most optimal way it could be done. And on top of all this there are also the limitations of the IEC protocol itself.

Custom IEC Protocol Routines

If you write your own routines to talk the IEC protocol, you can optimize a few things. This will require a deep understanding of the IEC protocol and probably the IEC communications portion of Commodore DOS as well. My IEC bus notes provide a brief overview and lots of references; the best one to start with is probably IEC disected [sic], particularly the reprint it contains of Jim Butterfield's article from the July 1983 Compute!, "How the VIC/64 Serial Bus Works." Inside Commodore DOS is a good reference for the DOS in the 1541 and similar drives, including their internals.

You need to allocate some sort of "endpoint address" for your code in the drive so that you can talk to it. I can see three places this could be done:

  1. Try and piggyback on the DOS command channel (15) protocol, the one to which you send commands like SCRATCH:MYFILE. This seems overly difficult and adds the most overhead.
  2. Use the drive's device number (typically 8 for the first drive), but repurpose one of the DOS channel numbers for your own use. Re-using 0, 1 or 15 would break DOS, but you could grab one of the data communications channels between 2 and 14. That would make it incompatible with programs trying to use that channel for I/O, and also might add some overhead for the channel management.
  3. Allocate a new device number for your code. Adding, say, 12 to the disk drive's device number would put your device number in the generally-unused 16-30 range and you can then use any protocol you like (within IEC limits) to talk to that device without worrying about interference with other devices.

If you follow this last plan, you can optimize your IEC protocol speed by making your system a pure command-response system: assert A̅T̅N̅, send a talk command to your device followed by the request data, reverse the transfer direction on the bus, and immediately read the response from your device. That's about as fast as bidirectional IEC communications can be done. There are still a couple of issues that will slow your communications here, though.

The first is that all devices on the bus must read and acknowledge each byte of the command you send. The timing also dictates that you must hold each data bit (individual bits are not ack'd, just bytes) for 20 μs, and you can't reduce this this without potentially losing other devices on the bus that need to keep up, so you end up being limited to a transfer rate of a few kilobytes per second for your outgoing command and data. Even worse, message setup and acknowledgements have very lose timeouts, up to a second, so a very slow device on the bus can delay messages drastically, even if you've programmed the C64 and your device to be able to handle this quickly.

The second is that, without blanking the screen, you are unable to communicate during the badline interval, a 40 cycle period occurring every eight raster lines (or even more often if you're using tricks to get more colors) when the CPU is paused to allow the VIC-II to read color memory. Data being read from the IEC bus by the C64 is clocked by the device sending the data and acknowledged only every eight bits, so if you're not available to read bits between the acknowledgements they get lost. The standard C64 DOS works around this by increasing the intra-bit delay to 60 μs, but that slows you down even further.

The only way around this that I can think of is to make your messages short enough to be able to complete the transfer between badlines and time them so that they start with enough time left before the badline to complete. But even here, you still have the potential issue of other devices on the bus slowing you down, as described above.

Custom Non-IEC Protocol

If you can guarantee that all the devices on the IEC bus are running your code (say, by making the user unplug any devices you're not programming) you've got the flexibility to change to a protocol more efficient than IEC and without its timing limitations.

Your main set of constraints is the connections of the pins on the IEC serial port itself. These are described (with a schematic) on page 13 of the C64/C64C Service Manual:

  1. S̅R̅Q̅I̅N̅: Input only. Connected to the F̅L̅A̅G̅ pin on CIA1 and thus requires a separate read from all other lines (which are on CIA2), though it can be programmed to generate an interrupt on the C64.
  2. GND: Ground. Obviously not usable for I/O.
  3. A̅T̅N̅: Output only, via CIA2 PA3.
  4. C̅L̅K̅: I/O via CIA2 PA6 and PA4.
  5. D̅A̅T̅A̅: I/O via CIA2 PA7 and PA5.
  6. R̅E̅S̅E̅T̅: Connected to the C64 reset line, and in practice not usable for communications since asserting it resets the C64.

Supercat came up with the brilliant suggestion of having the C64 clock the data transfers in to the C64 as well as out from it; this solves a lot of timing problems, particularly the badline one. Basically, so long as the device can keep up (which a 1571 ought to be able to, since it has little it must do but communicate if you program it appropriately) the C64 can clock data in and out at its maximum rate, ignoring pauses for badline, interrupts or anything else; the remote device will simply pause at the same time until the C64 is ready again.

Doing this, since you have three lines available and need only one input for the data, you can also do bidirectional data transfers in a SPI-like way: the other two lines become your clock and data outputs. (A̅T̅N̅, being output-only, must be used for either your clock or your data output; the other line and data input can be assigned to C̅L̅K̅ and D̅A̅T̅A̅ as you see fit.) The 1 kΩ pull-ups on all those lines are fast enough that your speed here will be limited only by how fast you can program the CPU to toggle those lines.

I'd suggest using D̅A̅T̅A̅ for input from the device to the C64; because it's read via bit 7 of the CIA2 PA register, there are some tricks you can use to maximize the speed at which you can do CPU-driven I/O. Not all these tricks apply exactly; for example you can't use an absolute-addressed INC of memory to toggle the clock because you don't have a serial port pin connected to bit 0 of a CIA parallel register¹, so you'd need to substitute something like an ADC followed by an STO. But there's still a lot you can do within the limitations of your hardware arrangement. The details of this would probably best be discussed in the 6502.org programming forum, followed by a question and answer posted here once more of the details have been hashed out.

I'm not sure how fast you could actually get this going, but with the badline and similar issues gone, bidirectional data transfers and some clever programming I'd think you could burst your forty-byte transfers at around 20 cycles per bit, allowing you to do a transfer in less than a millisecond, giving you plenty of time during the 16.66 ms frame to do other work.


¹You could work around this by connecting the drive to the user port on the C64, but that brings in its own host of issues. Still, it would probably be possible, with a lot of care, to build a cable that connects the drive to both and write your software in a way that maintains compatibility with normal drive usage.

| improve this answer | |
  • If you use /ATN in non-standard ways, it will be necessary to physically remove from the bus any devices that aren't aware of your protocol. If you leave /ATN idle, then other devices which have determined that communication isn't for them will ignore anything that happens on the bus until the next /ATN pulse. – supercat Mar 10 at 20:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.