Page 1 of 8

External RS-232 Interface, storage, and second screen.

Posted: Thu Jul 23, 2020 11:44 pm
by TomXP411

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. 

Key points:


  • PARALLEL interface. This will use 8 VIA data lines to carry a full byte in one clock cycle


    • 8 bits for data


    • 1 wire for clock (this is triggered once per byte, to let the UART know there is data waiting)


    • 1 wire for direction. Raising and lowering this line tells the UART whether the computer wants to read a byte or transmit a byte.




  • Buffering and Flow Control


    • 256 byte buffer per direction per channel. (So 1 channel uses 512 bytes. 2 channels use 1024 bytes, etc.)


    • 2 lines for buffer status: one line signals receive buffer has data. Another line signals transmit buffer is empty.




  • Command/Data interface:


    • Channel select (Selects serial ports, network port, file I/O, or external display)


    • a dedicated wire selects command mode, which is used for controlling the UART.


    • Baud Rate, data length, stop bits, parity


    • IP address (for TCP/UDP channels)


    • Directory and File selection (for file I/O)




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.  

** update: For the first prototype, I've selected the Raspberry Pi zero. I'm currently using two Pis, connected together, as my test harness. More updates at the end of the thread.

** Update 2: Pi works, but is slow. I simply can’t sense  the GPIO fast enough to do more than about 5-10KB/sec. I may be able to speed this up a little with an additional handshaking pin… stay tuned. 

 


External RS-232 Interface, storage, and second screen.

Posted: Thu Jul 23, 2020 11:55 pm
by TomXP411

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. 

 


External RS-232 Interface, storage, and second screen.

Posted: Fri Jul 24, 2020 12:17 am
by TomXP411

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. 


 


External RS-232 Interface, storage, and second screen.

Posted: Fri Jul 24, 2020 3:47 am
by BruceMcF

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.


External RS-232 Interface, storage, and second screen.

Posted: Fri Jul 24, 2020 11:32 pm
by TomXP411


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?

 

 


External RS-232 Interface, storage, and second screen.

Posted: Fri Jul 24, 2020 11:35 pm
by TomXP411


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. 


External RS-232 Interface, storage, and second screen.

Posted: Sat Jul 25, 2020 4:19 am
by Lorin Millsap
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

External RS-232 Interface, storage, and second screen.

Posted: Sat Jul 25, 2020 6:47 am
by BruceMcF


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

 


External RS-232 Interface, storage, and second screen.

Posted: Sat Jul 25, 2020 7:03 am
by BruceMcF


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).


External RS-232 Interface, storage, and second screen.

Posted: Sat Jul 25, 2020 8:27 am
by TomXP411


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.