Jump to content
TomXP411

External RS-232 Interface design

Recommended Posts

Posted (edited)

Based on this tread, I've started a design spec for an external UART interface. It would rely on a PIC or Arduino to convert the parallel data to serial, as well as provide buffering. A 256 bytes bi-directional buffer is expected. 

https://docs.google.com/document/d/1HwHCv2txDu91l3uozRH8hwBrq6Fw06lmrQl3kidKQ1c/

@BruceMcF and @Andre , you've had the most to say, so if you could review the doc and provide your input here, I'll refine the specification based on your suggestions.

My goal is to test this on a WDC 6502 development board, and once we have 6502 and Arduino code working, provide a PR to @Michael Steil with a KERNAL routine for direct access.

We'll probably provide 5 KERNAL routines:

  • Clear To Send: Returns 0 if send buffer is full. >1 if buffer is not full. (Returned value is bytes available.)
  • Write Byte (returns 1 if successful, 0 if buffer is full)
  • Data Waiting: returns 0-buffer size bytes.
  • Read Byte: returns first byte in buffer, 0 if buffer is empty. (You should always check Data Waiting before a read for binary data. Reading a null is fine for ASCII data.)
  • Set Address: Sets address register for read/write. Used to set baud rate, port number, or directly read modem pins.

Ideally, I'd like to see this implemented as device #2, using secondary address 10-13 for data and 14 for control.

For example:
OPEN 2,2,2: Opens the bit-banged serial port
OPEN 2,2,10, "9600" (Opens the first serial port at 9600 baud.)

I'm currently re-writing the spec to take your suggestions into account, as well as extend the design to allow for more flexibility.  

Edited by TomXP411

Share this post


Link to post
Share on other sites

Addressing: 

PB5 is tentatively the "Address" pin. Setting this pin during a write will tell the UART to read from or write to a different status register. This allows you to change the baud rate, port number, or directly access the RS-232 status pins. Address 0 is the read/write buffer. 1 is the port number, 2 is the status pin register, and 3 is the baud rate.

So to change baud rate to 9600:

  • Set PB5
  • Send $03
  • Clear PB5
  • Send $05
  • Set PB5
  • Send $00

The last two steps should be optional. I expect the read/write address to default to 0 after reading from or writing to the status registers. Or, if we choose not to immediately set it back, there should be a timeout of ~1s, after which, the address will be reset to 0. 

 

Share this post


Link to post
Share on other sites
Posted (edited)

Read Cycle:

This is where I'm having some heartburn. The Centronics specification calls for pin 10 to be ACK. This should be strobed after the receiving device (printer) successfully reads a byte. However, this is tied to CA1, which is (I think) an interrupt line on the CX16. 

So my thinking is to cycle the Data Waiting line when reading from the UART and cycle the Clear To Send line when writing to the UART:

So on a read cycle:

  1. Computer reads PB2. If High, data is waiting. If Low, return with no data.
  2. Computer Sets PB0 High (Read)
  3. Computer Sets PB3 Low
  4. Computer waits for PB2 Low
  5. UART sets data on PA
  6. UART sets PB2 Low
  7. Computer reads PA0-PA7
  8. Set PB0 High
  9. UART floats PA pins.
  10. UART sets PB2 based on receive buffer state.

A write cycle:

  1. Computer reads PB1. If High, data may be sent. If Low, return 0 (buffer full).
  2. Set PB0 LOW (write)
  3. Write PA0-PA7
  4. Set PB3 low.
  5. UART sets PB1 Low (read acknowledge)
  6. Computer floats PA pins.
  7. Set PB0 HIGH
  8. UART sets PB1 based on send buffer state

Should we also strobe CA1 during a read/write operation? If so, this could be configurable at runtime by setting a status register. The baseline implementation might rely on polling, while the advanced implementation might use an IRQ handler. 

Tentatively, I've added 6 IRQ flags:

  • IRQ on data received (this triggers for every byte)
  • IRQ on buffer not empty: Triggers once when data received. Does not trigger again until buffer is empty. This can reduce load on the host by allowing it to empty the receive buffer in one pass after receiving an interrupt. 
  • IRQ on buffer full: Triggers once when receive buffer is 75% full. Does not trigger again until buffer is (mostly) empty.
  • IRQ on TX buffer empty,: Triggers once when TX buffer is empty. 

 

Edited by TomXP411

Share this post


Link to post
Share on other sites
Posted (edited)

CA2 is the line that can be used as a strobe. It has eight possible settings in register $0C, four input and four output:

  • %000, Negative Active Edge Input
  • %001, Independent interrupt on negative edge input
  • %010, Positive Active Edge Input
  • %011, Independent interrupt on positive edge input
  • %100, Handshake Output
  • %101, Pulse Output
  • %110, Low output
  • %111, High output

Their detected events are read as 1 bits in the Interrupt Flag Register, and trigger an IRQ if the corresponding bit is set in the Interrupt Enable Register. In most cases, they are cleared by reading to or writing the I/O Register A, except the "independent" CA2 interrupts are only cleared when that bit in the IFR is set to 0. The IFR bit 7 is set if ANY of the 7 interrupt flags are set.

CA1 is an edge input detect, %0 for negative active edge, %1 for positive active edge.

As I've mentioned before, the Centronics specification is not for an Input/Output parallel port. The EPP specification in IEEE1284 is intended for that application. It uses Centronics Strobe as Read/Write, Centronics Ack as Interrupt, Centronics Busy as Wait/Ready, Centronics auto-linefeed as /Data Select, Centronics Initialize as /Reset (active low), and Centronics Select Printer as /RegisterAddress select. It does not use Centronics Paper Out - End, Select or Error/Fault.

For the EPP standard, connecting CA1 to Wait/Ready is inconvenient, since CA1 is an edge detect, so for the protocol you have to have it set to detect an active low transition before initializing the device, then when it is active low, flip it to active high before asserting the /data or /address strobe low, then when the high transition is detected that signals read to proceed, return it to detect active low transition.

So for EPP, you would rewire the Wait/Ready line to a GPIO, just as you would rewire the /Reset line to a GPIO, and simply set Wait/Ready to input and /Reset to output.

Actually, Centronics ACK should not be connected to CA1 either ... it is more convenient to do handle the LEVEL of the ACK on an input GPIO than to setup to detect one transition, then swap over to detect the other transition, then swap back ... it should be connected to Paper - Out / End. That's the line in a standard centronics parallel output routine where you want an interrupt available when it happens so you have the option of suspending the process and have the user change the paper.

Edited by BruceMcF
  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)
19 hours ago, BruceMcF said:

 

For the EPP standard, connecting CA1 to Wait/Ready is inconvenient, since CA1 is an edge detect, so for the protocol you have to have it set to detect an active low transition before initializing the device, then when it is active low, flip it to active high before asserting the /data or /address strobe low, then when the high transition is detected that signals read to proceed, return it to detect active low transition.

So for EPP, you would rewire the Wait/Ready line to a GPIO, just as you would rewire the /Reset line to a GPIO, and simply set Wait/Ready to input and /Reset to output.

Actually, Centronics ACK should not be connected to CA1 either ... it is more convenient to do handle the LEVEL of the ACK on an input GPIO than to setup to detect one transition, then swap over to detect the other transition, then swap back ... it should be connected to Paper - Out / End. That's the line in a standard centronics parallel output routine where you want an interrupt available when it happens so you have the option of suspending the process and have the user change the paper.

So CA1 would be the IRQ line and CA2 would be a read/write strobe? I'm good with that, and it fits right in to my model.

What I'm not clear on is how to strobe CA2 for a READ operation. Since we're not setting data on PAx (instead, setting all 8 pins to input),  how do you cause CA2 to change state? Do you just force the flag to %110, or do you write to PA, even though all of its lines are in the Read state?

 

 

Edited by TomXP411

Share this post


Link to post
Share on other sites
19 hours ago, BruceMcF said:

Their detected events are read as 1 bits in the Interrupt Flag Register, and trigger an IRQ if the corresponding bit is set in the Interrupt Enable Register

So, to be clear, we CAN deliberately read whether the transition has happened, but we can't poll the current state?

That's good enough, since all we care about for ACK/IRQ is a 0>1 or 1>0 transition. 

  • Like 1

Share this post


Link to post
Share on other sites

An external SPI option I think is a good compromise especially if an SPI 2,4, or 8 implementation can be done. The single SPI using a shift register should be adequately fast as well.


Sent from my iPhone using Tapatalk

Share this post


Link to post
Share on other sites
6 hours ago, TomXP411 said:

So CA1 would be the IRQ line and CA2 would be a read/write strobe? I'm good with that, and it fits right in to my model.

What I'm not clear on is how to strobe CA2 for a READ operation. Since we're not setting data on PAx (instead, setting all 8 pins to input),  how do you cause CA2 to change state? Do you just force the flag to %110, or do you write to PA, even though all of its lines are in the Read state?

The CA2 handshake is not "get ready, an action is coming", it's a signal that the a read or write of the Port A has occurred, and the two handshakes are built around that. If CA2 is in handshake mode, after a read or a write of Port A, CA2 goes low and stays low for until C1 receives it's clock edge.

So in handshake mode, CA2 is "Data Ready" when writing data to Port A ... this goes low until "Data Taken" is received on CA1.

In read mode, CA1 is "Data Ready" FROM the other device, and CA2 is "data taken" signaled to the other side, so it goes high when a "Data Ready" is received on CA1 and then goes low when the data has been read.

In pulse mode, CA2 goes low for one clock cycle after the read or write of PortA, rather than going high when CA1 detects the next edge.

So if you have a 6522 on both sides, you connect CA1.system1 to CA2.system2 and CA2.system1 to CA1.system2.

So if you want a master/slave set-up, you'd have DIFFERENT lines that signal "get ready for a write" or "get ready for a read" (R/W in EPP), and "I want to do a data packet transfer", (/DATA in EPP), and leave R/W and /DATA in their state while CA1 and CA2 handshake the individual byte transfers. When /DATA goes high, then then packet has been transferred.

A second signal would be used for STATUS (when R/W=1) and SETUP (when R/W=0), because those are individual byte transfers, not packets.

So in /Data mode, the loop only monitors "data taken" in writes and "data ready" in reads and the opposite of the pair is handled automatically when the write or the read of the port takes place. As maximum throughput, if the slave device is accepting or generating the data as fast as the 6522 in the master device, that throughput is a bit over half the throughput of an internal 65c02 block memory copy.

Now, with CA2 being used as the data ready line for writing data packets and the data taken line for reading data packets, it can't be used as the independent IRQ, so PB2 would be used as independent IRQ ... this takes the interface away from the parallel port interface, as PB2 would have to be jumpered through on a line that would be ground for a Centronics interface.

6 hours ago, TomXP411 said:

So, to be clear, we CAN deliberately read whether the transition has happened, but we can't poll the current state?

That's good enough, since all we care about for ACK/IRQ is a 0>1 or 1>0 transition. 

Yes. The Interrupt Flag register [IFR] is "1=happened, 0=didn't happen". So if CA1/CB1 is set to detect a negative edge, we can see whether a negative edge has happened since the last read or write of Port A. Or, if CA2/CB2 is set up in a negative edge independent interrupt mode, we can see whether a negative edge has happened since the last time that bit in the IFR was set to 0.

Each bit in the IFR can be set to trigger IRQ or not, based on whether the corresponding bit in the Interrupt Enable register [IER] is 1 or 0.

And the high bit in the IFR is high if any bit set to generate an IRQ is presently high, so when processing an IRQ, if you load the IFR, you can BPL out if that is not the source of an uncleared Interrupt or BMI out to the interrupt processor. If you have a 7 vector jump table for the interrupt handlers for the independent sources of an interrupt in the 6522, you can:

  LDA VIAx_IFR
  BPL ++
  AND VIAx_IER
  LDX #$0C
- LSR
  BCC +
  PHA
  PHX
  JMP (VIAx_Interrupt,X)
  PLX
  PLA
+ DEX
   DEX
   BPL -
++
  RTS
 

  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)
3 hours ago, Lorin Millsap said:

An external SPI option I think is a good compromise especially if an SPI 2,4, or 8 implementation can be done. The single SPI using a shift register should be adequately fast as well.

Aren't SPI2, 4 & 8 primarily for memory?

A single SPI using a single shift register would, of course, need a shift register with daisychain serial output, which  can act as a Serial In/Out, Parallel Read/Write shift register, which isn't what the 6522 provides. That's why you need two 6522's to implement a hardware SPI, as well as a quad XOR if you want to get all four modes.

But some SPI devices are half-synchronous, and generate a 0 when receiving data or ignore the data output when writing data. Others only generate a 0 when receiving data in a normal state or a 1 when in an abnormal state, so they only send #0 or #$FF when receiving data. For those devices, a R/W control line to switch the 6522 serial shift register between MOSI and MISO and to switch MISO to ALERT when in write mode would allow a single 6522 serial shift register to talk to those half-synchronous SPI devices.

And, obviously, if there was a SIPO shift register (eg, 74HC595) on the User Port, then with it tied to the correct clock and latch pin in the 6522, that would enable a hardware SPI, using the single shot byte output of the VIA serial shift register and the clock pulse output tied to the clock input of the serial shift register. That works for a User Port device that uses Port A as the parallel read or write, but only really works on the motherboard if there is a spare select pin in the built-in I/O select space.

Plus a generic SPI output needs to be set up to handle all four modes ... a dedicated interface to a MAX3100 only needs to handle the mode of the MAX3100, so possibly a four IC dongle or expansion card, with 74HC595, a quad inverter, a MAX3100 and a RS232C level converter (which can be simpler than the MAX charge pump chip on an expansion card which has +/-12v power available).

Edited by BruceMcF
  • Like 1

Share this post


Link to post
Share on other sites
Posted (edited)
4 hours ago, Lorin Millsap said:

An external SPI option I think is a good compromise especially if an SPI 2,4, or 8 implementation can be done. The single SPI using a shift register should be adequately fast as well.


Sent from my iPhone using Tapatalk

SPI 8 would be amazing, but you need 8 wires each direction, plus clock and Slave Select lines. 

4 bit SPI is perfectly doable right now. 

  • CA2 SCLK
  • PA0-3 MISO
  • PA4-7 MOSI
  • PB0-3 SS (4 devices)
  • PB4-5 free (I'll still use them for CTS and Data Ready)

This actually the same speed for full duplex communication as the 8-bit half-duplex protocol I suggested, or EPP. But when the data is going mostly one-way, it's not as efficient. 

The advantage is that it's simple to implement, and there's lots of stuff out there that already implements it in hardware. 

 

 

Edited by TomXP411

Share this post


Link to post
Share on other sites

On addressing, remember that the state of all PortB output lines can be set in parallel. Also, it isn't necessary at the outset to allocate things to the Centronics output-only parallel printer port pint allocations when the User Port is not ACTUALLY a Centronics parallel port, but rather a set of VIA pin allocations that can emulate a Centronics parallel port.

That suggests to me a dedicated "reset register to 0" line. The CA1/CA2 handshake is dedicated to the "/Port0" operations.

So, to read from a "high" register, A=x, Y=register address. Note, "Wait/Ready" is GPIO input, not CA2.

(1) R/W=0, /Address=0, /Data=1, /Port0=1
(2) Wait/Ready=0?
(3) Write Register address
(4) Wait/Ready=1?
(5) R/W=1, /Address=1, /Data=0, /Port0=1
(6) Wait/Ready=0?
(7) Read register contents
(8) Wait/Ready=1?
(9) Return, A=contents, Y=register address

So, to write from a "high" register, A=contents, Y=register address:

(1) R/W=0, /Address=0, /Data=1, /Port0=1
(2) Wait/Ready=0?
(3) Write Register address
(4) Wait/Ready=1?
(5) R/W=0, /Address=1, /Data=0, /Port0=1
(6) Wait/Ready=0?
(7) Write register contents
(8) Wait/Ready=1?
(9) Return, A=contents, Y=register address

To write a packet:

(1) R/W=1, /Address=1, /Data=1, /Port0=1
(2) Read Port A (to clear CA1)
(3) R/W=1, /Address=1, /Data=1, /Port0=0
(4) CA1=1->0?
(5) Read data (clears CA1)
(6) If more data, goto 4
(7) Return, A=number of bytes not successfully sent (0 on success)

To read a packet:

(1) R/W=1, /Address=1, /Data=1, /Port0=1
(2) Read Port A (to clear CA1)
(3) R/W=1, /Address=1, /Data=1, /Port0=0
(4) CA1=1->0?
(5) Read data (clears CA1)
(6) If more data, goto 4
(7) Return, A=number of bytes not successfully sent (0 on success)

This requires:
(1) D0-D7 = Port A D0-D7
(2) R/W, GPIO Output
(3) Wait/Ready, GPIO Output
(4) /Address, /Data, /Port0, three GPIO Output
(5) Port0 UART Ready, CA1
(6) Port0 System Performed, CA2
(7) UART IRQ, CB2
(8) Reset UART, GPIO Output

So that fits the User Port (with CB2 jumpered in), as it is 8 data lines in Port A, the available CA1,CA2, CB2, and 6 Port B GPIO, when six are available.

As far as potential compatibility with an EPP interface, that's out, but as far as potential compatibility with a device that is designed to work over an EPP protocol but know that there may be a UART using the same port ... that's feasible, all it takes is allocating it's pseudo registers to $10-$1F, and having it ignore any reads or writes directed to any lower registers. For that:
Data0-Data7 = EPP Data0-Data7
R/W=EPP R/W output
Wait/Ready = EPP Wait/Ready input
/Address = EPP /Address output
/Data = EPP /Data output
/Reset UART = EPP /Reset
/Interrupt = /CB2 = EPP Interupt
/Port0 = EPP Not Used
/CA1 = EPP Not Used
/CA2 = EPP Not Used

 

Share this post


Link to post
Share on other sites
Posted (edited)
3 hours ago, TomXP411 said:

SPI 8 would be amazing, but you need 8 wires each direction, plus clock and Slave Select lines. 

4 bit SPI is perfectly doable right now. 

  • CA2 SCLK
  • PA0-3 MISO
  • PA4-7 MOSI
  • PB0-3 SS (4 devices)
  • PB4-5 free (I'll still use them for CTS and Data Ready)

This actually the same speed for full duplex communication as the 8-bit half-duplex protocol I suggested, or EPP. But when the data is going mostly one-way, it's not as efficient. 

The advantage is that it's simple to implement, and there's lots of stuff out there that already implements it in hardware.

If using CA2 pulse as SCLK (which works), that is:

  • PA0-3 MOSI
  • PA4-7 SS (4 devices)
  • PB0-3 MISO

... because if you put MOSI and MISO on Part A, CA2 pulses for every write AND for every read, so bidirectional read/write (as when status of the write is returned on MISO) becomes impossible. Having a SS mask somewhere, you can have the four bits out in A, "AND SS_setting : STA VIAxPortA " and the pulse is triggered, so you can read PortB for the input.

With one line, you only need one bit of Port A, since CA2 can work as the SCLK and the serial shift register can be the input by tying CA2 to CB1. Since SPI shifts low bits first and the 6522 VIA enters input at bit0 and shifts it up toward bit7, it's most convenient to have PA0 be MOSI ... you also would want a page that has the reversed bits at each page address ... eg, address Page+%00110101 contains %10101100.

  LDX #7
 
STA PortA
- LSR
   STA PortA
   DEX
   BNE - 
   LDX SREG
   LDA BItFlip,X
   RTS

... which at 110clocks is appreciably faster than bit banging the SCLK but a hardware PHI2/2 SCLK SPI s 11 clocks JSR/RTS, 8 clocks write output byte, read input byte and 16 clocks to transfer, or about 35 clocks. So throughput is about three times faster with full hardware SPI than with CA2 output pulse SPI.

A 6522 SPI is natively Mode3 ... a downward pulse for a clock and latching the outputs/inputs at the trailing edge of the clock. Mode0, with an upward pulse for a clock and latching outputs/inputs on the leading edge, requires an AND gate and a control bit, to hold the clock low and manually release it so the leading edge of the CA2 pulse is treated as the trailing edge of a "long" first clock and the trailing edge is the leading edge of the next "long" clock.
  LDX #7
  LDY 1stState
  STY PortB
 
STA PortA
  LDY FollowState
  STY PortB
- LSR
   STA PortA
   DEX
   BNE - 
   LDX SREG
   LDA BItFlip,X
   RTS
... which adds another 16 clocks to the overhead for routine. Then a second control bit which inverts SCLK when set (through an XOR) would give all four modes. In both of those circuits, it is the unmodified CA2 which is connected through to the CB1. Two PortB outputs for mode control and four for DeviceSelect exactly fits the six available.

If PB0-3 are the Device Select, PB4 the CA2 Through/Filter, and PB5 the clock invert, the Setup is A=Mode#, Y=Device#
SETUP_SPI:
   AND #3
   TAX
   LDA SPIMODE0,X
   ORA SPIDEVICE,Y
   STA 1stState
   ORA #$10
   STA FollowState
   RTS
SPIMODE: !byte $00,$30,$20,$10
SPIDEVICE: !byte $01,$03,$07,$0F

Edited by BruceMcF

Share this post


Link to post
Share on other sites

Okay... so rather than keep discussing this in theory, I'm thinking about grabbing a W65C816SXB from Amazon (the WDC 65816 prototype board.) It has a VIA and appears to have all 16 PA/PB pins accessible through the VIA connector. So I can use that to test VIA communications code. 

  • Like 1

Share this post


Link to post
Share on other sites

Go for it. My physical test units are whichever of my C64's still work, if either of them do, and they are on the wrong  continent for me to access, so I am focusing on bringing up my port of eForth.

Share this post


Link to post
Share on other sites

Sorry for the late reply.

I'm totally with @BruceMcF about the handshake mode of the CA1/CA2 pins of the VIA. Pls see the VIA datasheet for the details how it works.

On the pin assignment I am not clear what the difference is between PB4/ACK and CA1 (or CA2?) Is ACK and output or input?

On SPI - you can have an easy SPI interface with the VIA using a single ser-2-par shift register, and XORs for other modes. See here http://www.6502.org/users/andre/csa/spi/index.html

 

  • Like 1

Share this post


Link to post
Share on other sites

It's a little confusing because the pinout is given from board side rather than connector side, and the block header is not numbered in the same way as the DB-25.

CA1 is on DB-25 pin 10, which is an input pin, ACK in the Centronics parallel port, Interrupt in the enhanced parallel port (rising edge).
CA2 is on DB-23 pin 11, which is an input pin, BUSY on the Centronics parallel port, WAIT/READY on the enhanded parallel port.

I don't know if ACK is an edge or a level, but in any event since it is a response to the data being written, you can be sure that you are ready for it if you need to set up CA1 to detect it.

CA2 on BUSY is a mistake for the Centronics parallel port, because BUSY is not an event, it is a state, so it should be connected to GPIO. It makes more sense to put CA2 on paper-out. Then even if you miss the edge, the BUSY will be high and the printer itself should also be indicating paper-out when we look to see why it isn't printing.

PB4 is on DB-25 pin 15, an input pin, which is Error|Fault for Centronics (not used in EPP). If it is low, it means no error. If BUSY is high, you check whether Error is also high to determine whether it makes sense to wait for the printer to stop being busy, or whether to tell the user that there is a problem with the printer.

Share this post


Link to post
Share on other sites

So at this point, I've given up on the WDC prototype board. i've tried to contact WDC a couple of times with questions, and they have never returned a single one of my messages. I can't even download the support software to make the board run. At this point, I'll probably have to just prototype my ideas on Arduino using software and wait for the real hardware before building the 6502/6522 side of things. 

  • Sad 1

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...

Important Information

Please review our Terms of Use