Close

(Dramatically) Improving Comms Speed

A project log for rosco_m68k

A full-featured Motorola 68k retro computer, starring a 68010 running at 10MHz

ross-bamfordRoss Bamford 06/07/2020 at 18:560 Comments

One of the things that's become an increasing problem on this project is the MC68901 Multi Function Peripheral. Way back when I started this project (actually when I ordered the very first MC68010 for it!) I ordered a 68901, almost on a whim, as it seemed like a very capable chip that would take care of a lot of problems for me. To an extent that turned out to be true, but the venerable MFP brings with it more than its fair-share of problems too.

The MFP was introduced sometime around 1983/84 and was one of the earlier 68000-specific peripherals, replacing a bunch of earlier 6800 legacy peripherals. On paper, it's a great chip - it sports a vectored interrupt controller, programmable timers, interrupt-capable GPIO with various trigger modes, and a USART. The fact that it packs so much into a small (48 pin DIL) package was one of the things that attracted me to it. 

Unfortunately, although it does a lot of different stuff, it's very much a "Jack of all trades, master of none" - it doesn't really excel at any of the various tasks. As an interrupt controller it's okay, and the programmable timers are quite useful (especially if you have need to use the advanced event counting and PWM modes). The GPIOs are okay for certain tasks, but not fast enough for, for example, bit-banging SPI (I think we measured them flat-out under software control at around 133KHz). And that's fine, because that's not what they're designed for. For interfacing legacy (read 6800) peripherals into the vectored interrupt scheme they're pretty great.

The UART (well, USART) is what really lets the 68901 down. Specifically, it's slow. This is largely due to the fact that it is totally useless unless used with the divide-by-16 baud rate clock, and when that clock is generated by the MFP itself (the recommended configuration from a Motorola application note) there's an additional divide-by-four pre-scaler. Coupled with the (necessary) divide by 16 this limits the baud rate that's usable to 9600 (if one cares about using POSIX rates, 28800 is possible otherwise but not supported by some of the support software).

As an aside, you could in theory drive the BRG from an external clock, and avoid the divide-by-16 counter, but of course this ups the component count and requires another clock (rather than just a crystal). The maximum one can use is 4MHz so a standard 3.6864MHz clock could be supplied and who knows, it might work well at higher speeds. Speed isn't quite the whole story though.

Programming the UART on the MFP in an interrupt-driven scheme is also fraught with problems. It boils down to the fact that, because of the way the registers are designed, there's no good way (that I've found at least) to safely implement a transmit ring buffer without risking deadlock. Because of the way pending interrupts are handled, you cannot be assured that a pending "buffer empty" interrupt that occurs while the interrupt is disabled will actually be raised once they are re-enabled. This leads to an awkward two-step dance of fully disabling the transmitter while trying to work out if the character to be sent should be buffered or just sent directly to the transmit buffer.

To make things worse, the buffer on both transmit and receive are only a single byte in size, and the slow GPIO (coupled with the pretty lax interpretation of CTS by the FTDI chips) means that "hardware" flow control (under software control - the MFP doesn't provide any help with this) is pretty much useless.

So to cut a long story short(er), I've been getting pretty frustrated with this, both from the UART perspective and also from the point of view of wanting to get SD cards and other SPI devices working. With this in mind, I started looking into the MC68681 DUART.

The 68681 is a slightly newer chip than the 68901 (arriving in late 1985), and was likely designed by some of the same people. In fact, when reading the data sheet and working with the chip, it definitely appears that some of the design choices came about as a direct result of lessons learned from the 68901. As you'd expect, it fully supports the 68k asynchronous bus architecture, and can do vectored interrupts.

It's a very capable chip: two completely separate UARTs, parallel input and output ports (6 and 8 bits, respectively) and full hardware flow control.  I received a bunch of 'em last week and got straight to work on the integration. And of course, it works perfectly. I've now got a (proof of concept, at this point) working Kermit upload at 115,200 BPS (quite a jump from 9600!)

The current design looks like this (initially this is going be an expansion for the rosco):

Unfortunately, the shadow of the 68901 is long, and this is still hampered by some of the decisions I made when initially integrating the MFP. This can't use vectored interrupts at the moment, because the MFP greedily takes all of odd IO space. Vectored interrupts require a vector on the low-order data lines, and that's just not possible yet. So for now this will use auto vectoring (one of the GALs takes care of the VPA line for this). DTACK is another area where the MFP over-steps it's boundaries (due to my design though, not the chip itself), hence the jumper on the DTACK line - the point of that is to make sure this expansion is compatible with both current and future iterations of the main system design.

In terms of code, the initial testing for this has been great. Once I'd spent a little while with the data sheet getting to know the chip getting it to 57K6 was fairly easy using its ability to directly use it's own built-in timers to generate a baud clock from an external 3.6864MHz crystal. The final step to 115K2 was a little less obvious, but googling around I found the answer - one simply has to enable "test" features of the IC, but reading from a register the data sheet explicitly says not to read from! This appears to be a common thing, and seems to work fine. I'm keeping the option of dropping back to 57K6 in my pocket though, just in case...

This is the basic initialisation code, plus polled send and receive. Interrupt driven is next on the list to implement.

INIT_SERIAL_HANDLERS::

    move.b  #$0,DUART_IMR             ; Mask all interrupts
    move.b  #$93,DUART_MR1A           ; (Rx RTS, RxRDY, Char, No parity, 8 bits)
    move.b  #$07,DUART_MR2A           ; (Normal, No TX CTS/RTS, 1 stop bit)

    move.b  #$60,DUART_ACR            ; Set 0, Counter, X1/X2, /16
    move.b  DUART_CRA,D0              ; Enable undocumented rates
    move.b  #$66,DUART_CSRA           ; 1200 per spec, uses counter instead

    move.b  #0,DUART_CUR              ; Counter high: 0
    move.b  #2,DUART_CLR              ; Counter  low: 2  (115.2KHz)
    move.b  R_STARTCNTCMD,D0          ; Start count

;   Debug - output clocks on OP2 for scope
;    move.b  #%00000011,DUART_OPCR     ; RxCA (1x) on OP2

    move.b  #%00000101,DUART_CRA      ; Enable TX/RX

    ; TODO CTS/RTS Not yet working - figure out how to lower RTS!
    move.b  #$ff,W_OPR_RESETCMD       ; Clear all OP bits (lower RTS)
    move.b  #0,W_OPR_SETCMD
    rts


RECVCHAR::
    btst.b  #0,DUART_SRA
    beq.s   RECVCHAR
    move.b  DUART_RBA,D0
    rts

SENDCHAR::
    btst.b  #3,DUART_SRA
    beq.s   SENDCHAR
    move.b  7(A7),D0
    move.b  D0,DUART_TBA
    rts

Discussions