• Please review our updated Terms and Rules here

Is there an RX01 Emulator?

Someone named Kirk brought one to VCF-West but I missed meeting him and didn't get his contact info. I'm trying to retrieve it now from some of the exhibitors. Hopefully I'll have info for you soon.

Jack
 
Folks,

I built Bela's RX02 emulator : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32765 . I have it working somewhat on the VT278, but no luck yet on the RX8E or RXV21. I didn't try the RX11 since it wasn't convenient. On the Decmate, I can boot and read RX01 and RX02 disks with no problem, however writing is problematic. I can only write properly to RX01s in bytemode format. I can't even read on the 11 or 8. I think it's time to haul out the logic analyzer and see what the deal is. I've rechecked all the wiring/resistors/connector pinning/2N7000 operation (read they are static sensitive) but that is all good. It must be software!

Here are a few more photos of the build:
http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32768
http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32769
http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32764

I used an Arduino Mega protoshield as Bela did. It is perfect because it has a DIP area for the 74HC00 and surface mount area for the 74LVC14. There is just enough room otherwise for the six 2N7000s and the 180/390 ohm resistors and 40 pin header. The Due at 3.3V is somewhat convenient because the SD card is 3.3V. The 3.3V high level still satisfies the 5V TTL inputs of the LCD, so that is good. I put a DB25F on the back like a teacart RX02 and also a 40 pin female header just like the normal rackmount RX02.

Has anyone else built this yet and have any experiences to share?


Lou

Hi Lou

Interesting timing. I've also built one prototype (no were near as good as yours) but I did learn a few things from it. The first thing is that I have a lot to learn about doing PCB's :)

I chose to use the Due because in Bela's notes said that he was having trouble with the mega after running for a while. Bela's design appears like a nice evolution from Chucks original board but he didn't put any caps on the chips. Also there should be some on the power supply coming in. I looked at the signals with a scope and they looked like crap. I was also getting SD card errors from what I believe was noise.

It turns out that Bob Rosenbloom also had decided do a board also. I've compared notes with him and he designed a very nice shield for the Due. Everything is nicely isolated and has separate regulators that can be enabled. We're using a AdaFruit TFT which has a SD card socket also. It should make for a better UI but we also have the headers for the keypad and LCD to support Bela's original design. We're going to put all the files up once we get something running.

Kirk
 
Kirk,

Fantastic! So you and Bob have both also built one of these. What controllers have you had success with?

Yes, I should also add caps at the two ICs. I would normally always do that, but I didn't this time, mostly because Bela didn't. I do have plenty of power supply input filtering on the power supply board. It is oversized, capable of delivering 4A. I am powering everything off this one supply. It connects to the 5V rail on the Arduino, which then powers the 3.3V regulator on that. I have a removable jumper for the 5V connection to the signal line resistors. That way I can unplug the power supply, pull the jumper, and then power the Arduino through the programming cable. That all seems to be fine. My reads and writes to the SD card are all fine.

I will scope the signals to see how clean they look.

I believe that for one or two of something it is not worth my time to make a PCB, so I wire wrap things. The Mega protoshield works perfectly with the Due, and so I built everything up on that. I like clean looking front panels, and so remotely mounting the SD card slot from the Due and also mounting the LCD in the panel was much cleaner (and cheaper) than using a stack of shields.

After basic scoping of the signal cleanliness, my next step will be to watch the back-and-forth between a controller and the emulator with the logic analyzer. That was very helpful when I troubleshot Reinhard's RL02 emulator. Of course I will post my findings here.

This should be a fantastic tool, so let's get it running well!

Lou
 
I did my debugging with an RX211 but I didn't have what I'd call success :) It's kind of a long story..

I agree that wire wrapping one or 2 off prototypes is the way to go. But recently I bought a PCB Mill and have been wanting to learn PCB layout. I took the schematic and did the layout in Eagle and milled my first PCB. In doing so I learned a lot when I was debugging. One of the things I did wrong was make the traces to thin. While I was debugging every time I'd pull the connectors off a trace might fracture and I'd have a random failure to track down. I was just in the process of starting on version 2 of the board when I talked to Bob. Since Bob lays out PCB's for a living I decided it was smart to combine efforts with him :)

So to answer your original question - I had minor success with the RX211. I was able to start commands and the emulator would start responding but with the problems I decided it was more time effective to just move onto version 2. My impressions of the emulator is that there isn't a lot to the RX211 (just a bunch of shift registers :)) and that if the signals are clean things should work. The Arduino code looks reasonable but there hasn't been a lot of use so there are going to be things to come up that need to be fixed with the logic analyzer as you noted.

On a side note I wanted to start with the RL02 emulator and actually bought all the parts for that. I looked closely at your posts and really liked your build. After reading your problems with signal integrity I decided maybe it was smart to start with something a little less ambitious :) That's when I decided to do the RX02 first.

I'd certainly like to work closely with you on getting this going. I'm thinking we should do a github repo for the code. I'm already going to be making additions for the TFT support but am going to follow Bela's structure so it will be a configurable addition that will not break the existing UI stuff.

Kirk,

Fantastic! So you and Bob have both also built one of these. What controllers have you had success with?

Yes, I should also add caps at the two ICs. I would normally always do that, but I didn't this time, mostly because Bela didn't. I do have plenty of power supply input filtering on the power supply board. It is oversized, capable of delivering 4A. I am powering everything off this one supply. It connects to the 5V rail on the Arduino, which then powers the 3.3V regulator on that. I have a removable jumper for the 5V connection to the signal line resistors. That way I can unplug the power supply, pull the jumper, and then power the Arduino through the programming cable. That all seems to be fine. My reads and writes to the SD card are all fine.

I will scope the signals to see how clean they look.

I believe that for one or two of something it is not worth my time to make a PCB, so I wire wrap things. The Mega protoshield works perfectly with the Due, and so I built everything up on that. I like clean looking front panels, and so remotely mounting the SD card slot from the Due and also mounting the LCD in the panel was much cleaner (and cheaper) than using a stack of shields.

After basic scoping of the signal cleanliness, my next step will be to watch the back-and-forth between a controller and the emulator with the logic analyzer. That was very helpful when I troubleshot Reinhard's RL02 emulator. Of course I will post my findings here.

This should be a fantastic tool, so let's get it running well!

Lou
 
I took a different path on a project like this. I used a Microsemi SmartFusion FPGA/ARM to make a controller/peripheral emulator. http://www.ricomputermuseum.org/Home/equipment/dec-pdp-8e/making-an-omnibus-peripheral-emulator

It is in the proof of concept stage now, but it will emulate an Omnibus PC05 paper tape reader. The time sensitive part of the bus interface is in the FPGA, and everything else is emulated with and application running under Linux in the ARM in the FPGA. Since the RX8E/RX01/RX02 does not use data-break I could add emulation with additional address decoding and skip logic in the FPGA and software in the ARM. My idea was to read the floppy image into RAM from flash, and have a flag that indicated if the floppy image in RAM was modified. If the processor PDP-8 RUN state was turned off the RAM image would be copied back to flash.

There is likely no practical limit to the number of controllers/peripherals that the FPGA and ARM could emulate at the same time.

The same concept could be used with Q-bus, but I haven't done that.
 
Nice. Here's something for Unibus someone did a while back:

https://web.archive.org/web/20091026230446/http://geocities.com/saipan59/dec/rx11_emu.html

I took a different path on a project like this. I used a Microsemi SmartFusion FPGA/ARM to make a controller/peripheral emulator. http://www.ricomputermuseum.org/Home/equipment/dec-pdp-8e/making-an-omnibus-peripheral-emulator

It is in the proof of concept stage now, but it will emulate an Omnibus PC05 paper tape reader. The time sensitive part of the bus interface is in the FPGA, and everything else is emulated with and application running under Linux in the ARM in the FPGA. Since the RX8E/RX01/RX02 does not use data-break I could add emulation with additional address decoding and skip logic in the FPGA and software in the ARM. My idea was to read the floppy image into RAM from flash, and have a flag that indicated if the floppy image in RAM was modified. If the processor PDP-8 RUN state was turned off the RAM image would be copied back to flash.

There is likely no practical limit to the number of controllers/peripherals that the FPGA and ARM could emulate at the same time.

The same concept could be used with Q-bus, but I haven't done that.
 
Folks,

I had a short bit of quality time at the bench tonight : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32802

The signal lines are all clean when examined on the scope, so next was to start watching the controller/emulated drive interaction with the logic analyzer. I am on the RXV21 for tonight's testing. After INIT L is asserted by the RXV21, the drive eventually asserts DONE L and then of all things, blasts out a 12 bit status word : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32803 . No, 12BIT L is not asserted, we are on an 11 and I checked the signal level all the way back to the Due to confirm that it should see 12BIT L not asserted. But it responds as if it was!

To further surely confuse the matter, the status word, when decoded, reports a double density disk in an RX01 drive : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32804. I am amazed that maybe the Decmate was able to swallow that, but for sure the RXV211 can't even digest this word and the RX8E wouldn't know what to do with a double density disk in an RX01.

So I think it's time for me to spend some quality time with the code listing of the program running in the Arduino.

Lou
 
Really great Lou. I hope to get a new board next week so I can contribute to the debugging effort.

Folks,

I had a short bit of quality time at the bench tonight : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32802

The signal lines are all clean when examined on the scope, so next was to start watching the controller/emulated drive interaction with the logic analyzer. I am on the RXV21 for tonight's testing. After INIT L is asserted by the RXV21, the drive eventually asserts DONE L and then of all things, blasts out a 12 bit status word : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32803 . No, 12BIT L is not asserted, we are on an 11 and I checked the signal level all the way back to the Due to confirm that it should see 12BIT L not asserted. But it responds as if it was!

To further surely confuse the matter, the status word, when decoded, reports a double density disk in an RX01 drive : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32804. I am amazed that maybe the Decmate was able to swallow that, but for sure the RXV211 can't even digest this word and the RX8E wouldn't know what to do with a double density disk in an RX01.

So I think it's time for me to spend some quality time with the code listing of the program running in the Arduino.

Lou
 
Late in 2015, after retiring from full time work, I dug out my half-finished-never-quite-got-to-work PIC18F4550-based RX02 emulator project from about 10 years ago. It was based on the Dickman RXM code base ported to a USB capable PIC18 device. The intent was to emulate the RX02 device interface with a USB serial backend to a PC running my TU58EM code, kind of like a version of serial disk for the RX interface, with TU58EM as the server. For various reasons I was never able to get a stable USB comm setup running on the PIC18 device, so I lost interest when I had no time to debug it further (still working full time then), and TU58EM was more than sufficient for my needs anyway.

Over the last couple of years I have done a number of Arduino based projects, both based on the entry level module and the Mega2560, which is now my new favorite go-to building block. So earlier this year I did an RX hardware interface shield for the Mega2560 platform. I used three 8641 devices (just like the RX drive does) to drive the interface lines (since I have a number of tubes of NOS 8641s available). The PCB was done thru OSHPARK (the purple PCB guys).

Shield: http://www.vcfed.org/forum/attachment.php?attachmentid=33007&d=1473140451

and ported my code base to it. All the low level bit-banging of I/O ports had been previously debugged by me on the PIC, and translating that to the Arduino was not hard to get it very high performance. Since the Arduino supports SD card / FAT32 filesystem access, modifying the storage to use local MicroSD was also done, so the RX disk images are stored on the local SD card. I also added a couple of FTDI USB/serial compatible headers to the shield (the Mega2560 has multiple hardware UARTs) so I can also implement file storage to a PC running TU58EM via a serial link in the future. I already have all the TU58 driver routines written for and debugged on the Arduino, they just need to be substituted in place of the SD card read/write routines.

All this discussion of the Torok RX02 emulator got me going on this again, as the Arduino proof of concept was more or less done. In looking at his implementation not much attention was paid to optimizing all the low level RX02 hardware interface bit banging routines, which is something I had done previously for the PIC. So I have been able to get the code running on just a standard OTS Mega2560 (not a higher performance ARM-based solution). The optimized Mega2560 is more than fast enough (RAM usage is 43%, FLASH usage is 12%).

I also did not implement the local LCD display for standalone operation (it could be done) but use a command line interface thru the USB serial backend. I use USB for power, so it is connected anyway, and the PC for a terminal emulation window.

Just this week I was able to get the full setup running on my 11/44 with RX211 (M8256) interface. Next step will be to do O/S testing (XXDP, RT11), and then move on to debug the interface on my PDP-8m with RX8E/28 interface board.

Here is a screen shot of the monitor screen:

http://www.vcfed.org/forum/attachment.php?attachmentid=33008&d=1473140454

But this is the more interesting log of the DEC RX02 performance tester diagnostic running on my 11/44 with RX211 attached to the emulator. NO ERRORS! Finally!

The DEC RX02/RX211 logic diagnostic does not yet pass (it flags some timing related failures, the emulator is too fast for some of the INIT processing). So a bit of debug to do there.

The test log (booted from the TU58EM XXDP disk) follows. Total wall clock run time was about 20 minutes start to finish one pass over two drives.

Code:
.R ZRXD??
ZRXDC0.BIC

DRSSM-G2
CZRXDC0-0-0
RX02 SS PERF EXER
UNIT IS RX02
RSTRT ADR 145702
DR>STA

CHANGE HW (L)  ? Y

# UNITS (D)  ? 2

UNIT 0
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ?
EXP WRD-CR (O)  0 ?

UNIT 1
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ? 1
EXP WRD-CR (O)  0 ?

CHANGE SW (L)  ? N


                      UNIT#0       UNIT#1
# SECTOR READS  (8)=  00000020174  00000020174
# SECTOR WRITES (8)=  00000010076  00000010076

                    UNIT#0  UNIT#1
CHECK SUM:               0       0
FILL-EMP BUFF LOG:       0       0
NO ERR BIT:              0       0
INTER-NO DONE ERR:       0       0
INTERRUPT ERR:           0       0
SEEK:                    0       0
CRC ERR:                 0       0
CRC BAD:                 0       0
READ ERR:                0       0
WRITE ERR:               0       0
DATA ERR:                0       0
DEL. DATA ERR:           0       0
HRD SEEK:                0       0
HRD CRC ERR:             0       0
HRD CRC BAD:             0       0
HRD READ:                0       0
HRD WRITE:               0       0
HRD DATA:                0       0
HRD DEL. DATA ERR:       0       0

ERR
CODE#   UNIT#0  UNIT#1
010          0       0
020          0       0
030          0       0
040          0       0
050          0       0
060          0       0
070          0       0
100          0       0
110          0       0
120          0       0
130          0       0
140          0       0
150          0       0
160          0       0
170          0       0
200          0       0
210          0       0
220          0       0
230          0       0
240          0       0
250          0       0
260          0       0

TRACK#  UNIT#0  UNIT#1
  0          0       0
  1          0       0
  2          0       0
  3          0       0
  4          0       0
  5          0       0
  6          0       0
  7          0       0
  8          0       0
  9          0       0
 10          0       0
 11          0       0
 12          0       0
 13          0       0
 14          0       0
 15          0       0
 16          0       0
 17          0       0
 18          0       0
 19          0       0
 20          0       0
 21          0       0
 22          0       0
 23          0       0
 24          0       0
 25          0       0
 26          0       0
 27          0       0
 28          0       0
 29          0       0
 30          0       0
 31          0       0
 32          0       0
 33          0       0
 34          0       0
 35          0       0
 36          0       0
 37          0       0
 38          0       0
 39          0       0
 40          0       0
 41          0       0
 42          0       0
 43          0       0
 44          0       0
 45          0       0
 46          0       0
 47          0       0
 48          0       0
 49          0       0
 50          0       0
 51          0       0
 52          0       0
 53          0       0
 54          0       0
 55          0       0
 56          0       0
 57          0       0
 58          0       0
 59          0       0
 60          0       0
 61          0       0
 62          0       0
 63          0       0
 64          0       0
 65          0       0
 66          0       0
 67          0       0
 68          0       0
 69          0       0
 70          0       0
 71          0       0
 72          0       0
 73          0       0
 74          0       0
 75          0       0
 76          0       0
CZRXDC0 EOP    1
    0 TOTAL ERRS

(Console)
^P

>>>H
  Halted at 025154

>>>
 
Last edited:
Folks,
After INIT L is asserted by the RXV21, the drive eventually asserts DONE L and then of all things, blasts out a 12 bit status word : http://www.vcfed.org/forum/album.php?albumid=341&attachmentid=32803 . No, 12BIT L is not asserted, we are on an 11 and I checked the signal level all the way back to the Due to confirm that it should see 12BIT L not asserted. But it responds as if it was!

Actually this is the correct behavior. The status word returned at the completion of any command execution is 12 bits on the RX211/RXV21 interfaces (as well as the RX8E/RX28, of course). Only the RX11/RXV11 is 8b.

Even the read command interface on the RX211/RXV21 is 12b (and not 8b) even tho 12b_mode is never asserted on an RX211/RX28.
 
Last edited:
Actually this is the correct behavior. The status word returned at the completion of any command execution is 12 bits on the RX211/RXV21 interfaces (as well as the RX8E/RX28, of course). Only the RX11/RXV11 is 8b. Even the read command interface on the RX211/RXV21 is 12b (and not 8b) even tho 12b_mode is never asserted on an RX211/RX28.

Don,

Thank you for this info. One of my next steps was to have been to hook up a real RX02 and watch it send the status word after init. I think my time is better spent digesting Bela's version of Chuck's code and so you have saved me a good bit of time.

Your shield looks great and your progress is very far along. Hopefully once you have it dialed in on the RX211 it will work fine on the RXV21.

Lou
 
Just for reference here is my current Arduino driver code for the RX02 interface, originally based on the CHD RXM, but with lots of optimization tweaks for the Arduino.

At some point I'll probably upload my entire RX02 emulator project to my GITHUB area alongside TU58EM et al, but here is a 'first look' for anyone interested in comparing vs the Bela implementation.

rx02_driver.h prototype file:
Code:
//
// rx02_driver - Simple RX02 driver external interface
//
// (C) 2013 Don North <ak6dn_at_mindspring_dot_com>
//
// 21 Oct 2015 - donorth - Initial code
// 27 Mar 2016 - donorth- Ported to Arduino from CCS/Microchip
//

#ifndef rx02_driver_h
#define rx02_driver_h



//
// public prototypes
//
void rx_initialize (uint8_t flag);
void rx_debug (HardwareSerial *serialPort);
void rx_print_state (HardwareSerial *serialPort);
void rx_set_unit_file (uint8_t unit, char *name);
void rx_function (void);



#endif // rx02_driver_h

// the end

rx02_driver.cpp implementation:
Code:
//
// rx02_driver - Simple RX02 driver
//
// (C) 2013 Don North <ak6dn_at_mindspring_dot_com>
//
// 21 Oct 2013 - donorth - Initial code
// 27 Mar 2016 - donorth- Ported to Arduino from CCS/Microchip
//



//
// includes
//
#include "my_project.h"
#include "led_driver.h"
#include "rx02_driver.h"
#include "sdcard_driver.h"



//
// definitions
//

// options

#define USE_BIT_BUILTINS 1 // use bit builtins BitRead() etc vs use logical bit shift/mask operations

// RXCS control/status bit definitions

//efine RXCS_GO        (1<<0)    // GO bit                       (reference only; not used in drive)
#define RXCS_FUNCTION  (7<<1)    // function bitfield                            
#define RXCS_UNITSEL   (1<<4)    // unit select
//efine RXCS_DONE      (1<<5)    // function complete            (reference only; not used in drive)
//efine RXCS_INTENB    (1<<6)    // interrupt enable             (reference only; not used in drive)
//efine RXCS_8BIT      (1<<6)    // 8b mode                      (RX8E/RX28 ONLY)
//efine RXCS_TR        (1<<7)    // transfer request             (reference only; not used in drive)
//efine RXCS_MAINT     (1<<7)    // maintenance mode             (RX8E/RX28 ONLY)
#define RXCS_DENSEL    (1<<8)    // density select
//efine RXCS_HEADSEL   (1<<9)    // head select
//efine RXCS_RX02     (1<<11)    // RX02 interface               (reference only; not used in drive)
//efine RXCS_EXTADDR  (3<<12)    // upper bits of phys address   (reference only; not used in drive)
//efine RXCS_INIT     (1<<14)    // initialize RX                (reference only; not used in drive)
//efine RXCS_ERROR    (1<<15)    // error flag                   (reference only; not used in drive)

#define RXFCN_FILL       (0)      // fill buffer
#define RXFCN_EMPTY      (1)      // empty buffer
#define RXFCN_WRSECT     (2)      // write sector
#define RXFCN_RDSECT     (3)      // read sector
#define RXFCN_SETMEDIA   (4)      // set media density
#define RXFCN_RDSTAT     (5)      // read status
#define RXFCN_WRDDSECT   (6)      // write deleted data sector
#define RXFCN_RDERROR    (7)      // read error code

// RXES status bit definitions

//efine RXES_CRC       (1<<0)    // CRC aka READ error
//efine RXES_SIDE      (1<<1)    // side                         (RX211/RX28 ONLY)
//efine RXES_PERR      (1<<1)    // parity error                 (RX11/RX8E w/RX01 ONLY)
#define RXES_ID        (1<<2)    // controller init done
#define RXES_RX02      (1<<3)    // set for RX02 drive           (RX28 w/ RX02 ONLY)
//efine RXES_ACLO      (1<<3)    // set for AC low               (RX211 w/RX02 ONLY)
//efine RXES_WPERR     (1<<3)    // set for write to WP drive    (RX11/RX8E w/RX01 ONLY)
#define RXES_DENERR    (1<<4)    // density error
#define RXES_DRVDEN    (1<<5)    // diskette double density
#define RXES_DELDATA   (1<<6)    // deleted data detected 
#define RXES_DRVRDY    (1<<7)    // drive ready
#define RXES_UNITSEL   (1<<8)    // unit selected                (RX211 w/RX02 ONLY)
#define RXES_WCOVF    (1<<10)    // word count overflow          (RX211 w/RX02 ONLY)
//efine RXES_NXM      (1<<11)    // nonexistent memory           (RX211 w/RX02 reference only; not used in drive)

// RX error codes

#define RXERR_SUCCESS   0000   // success, no error
//efine RXERR_DR0INIT   0010    // drive 0 failed to init
//efine RXERR_DR1INIT   0020    // drive 1 failed to init
//efine RXERR_STEPHOME  0030   // found home when stepping for init (RX01 ONLY)
#define RXERR_TRKERR    0040    // access to track > 76
#define RXERR_TRKFAIL   0050    // track not found
//efine RXERR_SELFDIAG  0060   // self diagnostic fail (RX01 ONLY)
#define RXERR_SECFAIL   0070    // sector not found
//efine RXERR_WRITEWP   0100   // write to a WP drive (RX01 ONLY)
//efine RXERR_SEPFAIL   0110    // no SEP clock in 40us
//efine RXERR_PREFAIL   0120    // preamble not found
#define RXERR_IDMFAIL   0130    // ID mark not found
//efine RXERR_CRCHEAD   0140   // CRC error on a header (RX01 ONLY)
//efine RXERR_TRKCMP    0150    // header track miscompare
//efine RXERR_IDMTRYS   0160    // too many tries for IDAM
//efine RXERR_DAMFAIL   0170    // data AM not found
//efine RXERR_CRCERR    0200    // CRC error on read
//efine RXERR_PERR      0210   // parity error on word from i/f to controller (RX01 ONLY)
//efine RXERR_RWFAIL    0220    // r/w failed maint test (RX02 ONLY)
#define RXERR_WCOVF     0230    // word count overflow (RX02 ONLY)
#define RXERR_DENERR    0240    // density error (RX02 ONLY)
#define RXERR_KEYERR    0250    // wrong key word for set density (RX02 ONLY)

// macros

// rx interface outputs set/clr

#if USE_BIT_BUILTINS
#define rx_set_done(xxx)     bitSet(PORTA,6)    /* digitalWrite(PIN_CTLR_DONE_H,HIGH) */
#define rx_clr_done(xxx)     bitClear(PORTA,6)  /* digitalWrite(PIN_CTLR_DONE_H,LOW) */
#define rx_set_error(xxx)    bitSet(PORTA,7)    /* digitalWrite(PIN_CTLR_ERROR_H,HIGH) */
#define rx_clr_error(xxx)    bitClear(PORTA,7)  /* digitalWrite(PIN_CTLR_ERROR_H,LOW) */
#define rx_set_aclo(xxx)     bitSet(PORTC,1)    /* digitalWrite(PIN_CTLR_ACLO_H,HIGH) */
#define rx_clr_aclo(xxx)     bitClear(PORTC,1)  /* digitalWrite(PIN_CTLR_ACLO_H,LOW) */
#define rx_set_shift(xxx)    bitSet(PORTC,2)    /* digitalWrite(PIN_CTLR_SHIFT_H,HIGH) */
#define rx_clr_shift(xxx)    bitClear(PORTC,2)  /* digitalWrite(PIN_CTLR_SHIFT_H,LOW) */
#define rx_set_out(xxx)      bitSet(PORTC,3)    /* digitalWrite(PIN_CTLR_OUT_H,HIGH) */
#define rx_clr_out(xxx)      bitClear(PORTC,3)  /* digitalWrite(PIN_CTLR_OUT_H,LOW) */
#define rx_set_datao(xxx)    bitSet(PORTC,5)    /* digitalWrite(PIN_CTLR_DATAO_H,HIGH) */
#define rx_clr_datao(xxx)    bitClear(PORTC,5)  /* digitalWrite(PIN_CTLR_DATAO_H,LOW) */
#define rx_set_request(xxx)  bitSet(PORTC,6)    /* digitalWrite(PIN_CTLR_TR_RQST_H,HIGH) */
#define rx_clr_request(xxx)  bitClear(PORTC,6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,LOW) */
// rx interface inputs test
#define rx_tst_pio(xxx)      bitRead(PINC,0)    /*  digitalRead(PIN_CTLR_DMA_MODE_L) */
#define rx_tst_dma(xxx)      (rx_tst_pio()?0:1) /* !digitalRead(PIN_CTLR_DMA_MODE_L) */
#define rx_tst_datai(xxx)    bitRead(PINC,4)    /*  digitalRead(PIN_CTLR_DATAI_H) */
#define rx_tst_12b(xxx)      bitRead(PINC,7)    /*  digitalRead(PIN_CTLR_12BIT_H) */
#define rx_tst_8b(xxx)       (rx_tst_12b()?0:1) /* !digitalRead(PIN_CTLR_12BIT_H) */
#define rx_tst_run(xxx)      bitRead(PINE,4)    /*  digitalRead(PIN_CTLR_RUN_H)) */
#define rx_tst_init(xxx)     bitRead(PINE,5)    /*  digitalRead(PIN_CTLR_INIT_H) */
#else
#define rx_set_done(xxx)     PORTA |=  (1<<6)  /* digitalWrite(PIN_CTLR_DONE_H,HIGH) */
#define rx_clr_done(xxx)     PORTA &= ~(1<<6)  /* digitalWrite(PIN_CTLR_DONE_H,LOW) */
#define rx_set_error(xxx)    PORTA |=  (1<<7)  /* digitalWrite(PIN_CTLR_ERROR_H,HIGH) */
#define rx_clr_error(xxx)    PORTA &= ~(1<<7)  /* digitalWrite(PIN_CTLR_ERROR_H,LOW) */
#define rx_set_aclo(xxx)     PORTC |=  (1<<1)  /* digitalWrite(PIN_CTLR_ACLO_H,HIGH) */
#define rx_clr_aclo(xxx)     PORTC &= ~(1<<1)  /* digitalWrite(PIN_CTLR_ACLO_H,LOW) */
#define rx_set_shift(xxx)    PORTC |=  (1<<2)  /* digitalWrite(PIN_CTLR_SHIFT_H,HIGH) */
#define rx_clr_shift(xxx)    PORTC &= ~(1<<2)  /* digitalWrite(PIN_CTLR_SHIFT_H,LOW) */
#define rx_set_out(xxx)      PORTC |=  (1<<3)  /* digitalWrite(PIN_CTLR_OUT_H,HIGH) */
#define rx_clr_out(xxx)      PORTC &= ~(1<<3)  /* digitalWrite(PIN_CTLR_OUT_H,LOW) */
#define rx_set_datao(xxx)    PORTC |=  (1<<5)  /* digitalWrite(PIN_CTLR_DATAO_H,HIGH) */
#define rx_clr_datao(xxx)    PORTC &= ~(1<<5)  /* digitalWrite(PIN_CTLR_DATAO_H,LOW) */
#define rx_set_request(xxx)  PORTC |=  (1<<6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,HIGH) */
#define rx_clr_request(xxx)  PORTC &= ~(1<<6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,LOW) */
// rx interface inputs test
#define rx_tst_pio(xxx)     ((PINC & (1<<0)) ? 1 : 0) /* (digitalRead(PIN_CTLR_DMA_MODE_L)==HIGH) */
#define rx_tst_dma(xxx)     ((PINC & (1<<0)) ? 0 : 1) /* (digitalRead(PIN_CTLR_DMA_MODE_L)==LOW) */
#define rx_tst_datai(xxx)   ((PINC & (1<<4)) ? 1 : 0) /* digitalRead(PIN_CTLR_DATAI_H) */
#define rx_tst_12b(xxx)     ((PINC & (1<<7)) ? 1 : 0) /* (digitalRead(PIN_CTLR_12BIT_H)==HIGH) */
#define rx_tst_8b(xxx)      ((PINC & (1<<7)) ? 0 : 1) /* (digitalRead(PIN_CTLR_12BIT_H)==LOW) */
#define rx_tst_run(xxx)     ((PINE & (1<<4)) ? 1 : 0) /* (digitalRead(PIN_CTLR_RUN_H)==HIGH) */
#define rx_tst_init(xxx)    ((PINE & (1<<5)) ? 1 : 0) /* (digitalRead(PIN_CTLR_INIT_H)==HIGH) */
#endif

// INIT/RUN status

#define RX_SAW_NONE     0               // saw neither RUN or INIT
#define RX_SAW_RUN      1               // saw RUN and not INIT

// emulation type

#define RX_TYPE_RX01    0               // RX02 in RX01 mode
#define RX_TYPE_RX02    1               // RX02 in native mode

// drive density

#define RX_DEN_SD       0                // single density mode
#define RX_DEN_DD       1                // double density mode

// data type

#define RX_NORMAL_DATA  0               // normal data
#define RX_DELETED_DATA 1               // deleted data

// disk definitions

#define RX_NTRKS        77L             // number of tracks per disk
#define RX_NSECS        26L             // number of sectors per track
#define RX_NBPS         128L            // bytes per sector, single density
#define RX_NUNITS       2               // number if units

#define RX_BUFFER_SIZE  (2*RX_NBPS)     // maximum size sector buffer for double density

// convenience macros

#define rx_sec_size(den)     (RX_NBPS<<(den))             // bytes per sector at density
#define rx_trk_size(den)     (RX_NSECS*rx_sec_size(den))  // bytes per track at density
#define rx_dsk_size(den)     (RX_NTRKS*rx_trk_size(den))  // bytes per disk at density

#define rx_get_bits(xxx)     (rx_tst_12b() ? 12 : 8)      // number of bits signaled by interface



//
// timing characteristics
//
#define RX02_ACTUAL_SPEED 0

// actual hardware timing
#if (RX02_ACTUAL_SPEED == 1)
#define RX_RDSTAT_TIME      (250)     // read status function
#define RX_INIT_TIME        (500)     // initialize floppy drive
#define RX_STEP_TIME         (10)     // track step time
#define RX_SETTLE_TIME       (20)     // seek settling time
#define RX_SEC_TIME          (20)     // sector access time
#define RX_RDERROR_TIME       (1)     // read error registers
#define RX_SETMEDIA_TIME  (10000)     // set media density
#endif // (RX02_ACTUAL_SPEED == 1)

// as fast as it can go
#if (RX02_ACTUAL_SPEED == 0)
#define RX_RDSTAT_TIME        (0)     // read status function
#define RX_INIT_TIME          (0)     // initialize floppy drive
#define RX_STEP_TIME          (0)     // track step time
#define RX_SETTLE_TIME        (0)     // seek settling time
#define RX_SEC_TIME           (0)     // sector access time
#define RX_RDERROR_TIME       (0)     // read error registers
#define RX_SETMEDIA_TIME      (0)     // set media density
#endif // (RX02_ACTUAL_SPEED == 0)

// compute seek time based on delta track amount
#define rx_seek_time(d) (RX_SEC_TIME + ((abs(d) == 0) ? 0 : RX_SETTLE_TIME + abs(d)*RX_STEP_TIME))



// PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE



volatile static uint8_t rx_init_seen; // set when RX INIT asserted (rising edge)

static jmp_buf rx_init_env; // environment to invoke when INIT is asserted (ie, reset/error condition)

static char fcn_name_list[][10] = { "FILL", "EMPTY", "WRSECT", "RDSECT", "SETMEDIA", "RDSTAT", "WRDDSECT", "RDERROR" };

static uint8_t rx_type = RX_TYPE_RX02; // default drive emulation, can be overwritten

struct drv_t {
    uint32_t dd[RX_NTRKS];  // normal or deleted data per track per sector (1 bit per sector 0..31)
    char     name[32];      // associated file name
    uint8_t  rdy;           // drive ready
    uint8_t  den;           // density
    uint8_t  ta;            // track address
    uint8_t  sa;            // sector address
    uint16_t len;           // read/write length
    uint32_t pos;           // read/write position
};

static struct rx_t {
    uint8_t  type;     // emulation type: RX01, RX02
    uint16_t cs;       // control/status image
    uint16_t es;       // error and status
    uint16_t bc;       // byte count
    uint8_t  wc;       // word count
    uint8_t  ecode;    // error code
    uint8_t  unit;     // active unit number
    uint8_t  den;      // density selected, DD or SD
    uint8_t  ta;       // track address
    uint8_t  sa;       // sector address
    uint16_t len;      // read/write length
    uint32_t pos;      // read/write position
    struct {
        uint8_t code;  // function selected (numeric 0..7)
        char *  name;  // function selected, ascii name id
    } fcn;
    struct drv_t drv[RX_NUNITS]; // drive specific parameters
    uint8_t buffer[RX_BUFFER_SIZE]; // sector buffer
} rx;

static HardwareSerial *debug = NULL; // debug serial output



//
// RX02 INIT assertion interrupt
//
static void rx_intr_init (void)
{
    rx_init_seen = 1;
    return;
}



//
// RX time delay routine
//
static void rx_timing (uint16_t ms_delay)
{
    const uint16_t ms_amount = 5;

    // check for delay requested
    if (ms_delay > 0) {

        // delay in 5ms increments, checking for INIT
        while (ms_delay > ms_amount) {
            if (rx_init_seen) { longjmp(rx_init_env, 10); }
            delay(ms_amount);
            ms_delay -= ms_amount;
        }

        // final small delay less than 5ms
        if (rx_init_seen) { longjmp(rx_init_env, 10); }
        delay(ms_delay);

    }

    // all done
    if (rx_init_seen) { longjmp(rx_init_env, 10); }
    return;
}



//
// wait until the RX_RUN or RX_INIT signal is asserted, or timeout occurs
//
static uint8_t rx_wait_run_or_init (uint32_t expire)
{
    uint32_t start = millis();

    do {
        if (rx_init_seen) { longjmp(rx_init_env, 1); }
        if (rx_tst_run()) { return RX_SAW_RUN; }
    } while (millis()-start <= expire);

    // count timed out with neither RUN or INIT
    return RX_SAW_NONE;
}

// wait forever until the RX_RUN or RX_INIT signal is asserted

static uint8_t rx_wait_run_or_init (void)
{
    do {
        if (rx_init_seen) { longjmp(rx_init_env, 2); }
        if (rx_tst_run()) { return RX_SAW_RUN; }
    } while (TRUE);
}



//
// receive a 12/8 bit word from the RX interface, msb first
//

#define _rx_recv_hs(xxx,yyy) { if (rx_tst_datai()) { delay_1c(); (xxx) |= (1<<(yyy)); } else { (xxx) &= ~(1<<(yyy)); delay_2c(); } delay_1c(); rx_set_shift(); delay_1c(); rx_clr_shift(); }

static uint16_t rx_recv12_hs (uint8_t handshake)
{
    uint16_t data = 0;

    rx_clr_out();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    noInterrupts();

    _rx_recv_hs(data,11);
    _rx_recv_hs(data,10);
    _rx_recv_hs(data,9);
    _rx_recv_hs(data,8);
    _rx_recv_hs(data,7);
    _rx_recv_hs(data,6);
    _rx_recv_hs(data,5);
    _rx_recv_hs(data,4);
    _rx_recv_hs(data,3);
    _rx_recv_hs(data,2);
    _rx_recv_hs(data,1);
    _rx_recv_hs(data,0);

    interrupts();

    if (rx_init_seen) { longjmp(rx_init_env, 3); }

    return data;
}

static uint16_t rx_recv8_hs (uint8_t handshake)
{
    uint8_t data = 0;

    rx_clr_out();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    noInterrupts();

    _rx_recv_hs(data,7);
    _rx_recv_hs(data,6);
    _rx_recv_hs(data,5);
    _rx_recv_hs(data,4);
    _rx_recv_hs(data,3);
    _rx_recv_hs(data,2);
    _rx_recv_hs(data,1);
    _rx_recv_hs(data,0);

    interrupts();

    if (rx_init_seen) { longjmp(rx_init_env, 4); }

    return uint16_t(data);
}

static uint16_t rx_recv (uint8_t n)
{
    if (n == 12)      return rx_recv12_hs(FALSE); else
    if (n ==  8)      return  rx_recv8_hs(FALSE); else
    if (rx_tst_12b()) return rx_recv12_hs(FALSE); else
                      return  rx_recv8_hs(FALSE);
}

static uint16_t rx_recv (void)
{
    if (rx_tst_12b()) return rx_recv12_hs(FALSE); else
                      return  rx_recv8_hs(FALSE);
}

static uint16_t rx_recv_hs (uint8_t n)
{
    if (n == 12)      return rx_recv12_hs(TRUE); else
    if (n ==  8)      return  rx_recv8_hs(TRUE); else
    if (rx_tst_12b()) return rx_recv12_hs(TRUE); else
                      return  rx_recv8_hs(TRUE);
}

static uint16_t rx_recv_hs (void)
{
    if (rx_tst_12b()) return rx_recv12_hs(TRUE); else
                      return  rx_recv8_hs(TRUE);
}



//
// transmit a 12/8 bit word to the RX interface, msb first
//

#define _rx_xmit_hs(xxx,yyy) { if ((xxx)&(1<<(yyy))) { delay_1c(); rx_set_datao(); } else { rx_clr_datao(); delay_2c(); } delay_1c(); rx_set_shift(); delay_1c(); rx_clr_shift(); }

static void rx_xmit12_hs (uint16_t value, uint8_t handshake)
{
    uint16_t data = value;

    noInterrupts();

    if (handshake) { rx_clr_request(); }

    rx_clr_shift();
    rx_set_out();
    delay_3c();

    _rx_xmit_hs(data,11);
    _rx_xmit_hs(data,10);
    _rx_xmit_hs(data,9);
    _rx_xmit_hs(data,8);
    _rx_xmit_hs(data,7);
    _rx_xmit_hs(data,6);
    _rx_xmit_hs(data,5);
    _rx_xmit_hs(data,4);
    _rx_xmit_hs(data,3);
    _rx_xmit_hs(data,2);
    _rx_xmit_hs(data,1);
    _rx_xmit_hs(data,0);

    delay_3c();
    rx_clr_datao();

    interrupts();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    rx_clr_out();

    if (rx_init_seen) { longjmp(rx_init_env, 5); }

    return;
}

static void rx_xmit8_hs (uint16_t value, uint8_t handshake)
{
    uint8_t data = value;

    noInterrupts();

    if (handshake) { rx_clr_request(); }

    rx_clr_shift();
    rx_set_out();
    delay_3c();

    _rx_xmit_hs(data,7);
    _rx_xmit_hs(data,6);
    _rx_xmit_hs(data,5);
    _rx_xmit_hs(data,4);
    _rx_xmit_hs(data,3);
    _rx_xmit_hs(data,2);
    _rx_xmit_hs(data,1);
    _rx_xmit_hs(data,0);

    delay_3c();
    rx_clr_datao();

    interrupts();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    rx_clr_out();

    if (rx_init_seen) { longjmp(rx_init_env, 6); }

    return;
}

static void rx_xmit (uint16_t data, uint8_t n)
{
    if (n == 12)      rx_xmit12_hs(data,FALSE); else
    if (n ==  8)       rx_xmit8_hs(data,FALSE); else
    if (rx_tst_12b()) rx_xmit12_hs(data,FALSE); else
                       rx_xmit8_hs(data,FALSE);
    return;
}

static void rx_xmit (uint16_t data)
{
    if (rx_tst_12b()) rx_xmit12_hs(data,FALSE); else
                       rx_xmit8_hs(data,FALSE);
    return;
}

static void rx_xmit_hs (uint16_t data, uint8_t n)
{
    if (n == 12)      rx_xmit12_hs(data,TRUE); else
    if (n ==  8)       rx_xmit8_hs(data,TRUE); else
    if (rx_tst_12b()) rx_xmit12_hs(data,TRUE); else
                       rx_xmit8_hs(data,TRUE);
    return;
}

static void rx_xmit_hs (uint16_t data)
{
    if (rx_tst_12b()) rx_xmit12_hs(data,TRUE); else
                       rx_xmit8_hs(data,TRUE);
    return;
}



//
// transmit an extended status word
//
static void rx_xmit_es (uint16_t data)
{
    if (debug) debug->printf("RX: rx_xmit_es(%04o)\n", data);

    if (rx_tst_dma() || rx_tst_12b() && rx.type == RX_TYPE_RX02) {
        // RX211/RXV21 or RX28 attached
        rx_set_done();
        rx_clr_request();
        rx_xmit(data, 12);
        rx_set_request();
    } else if (rx_tst_12b()) {
        // RX8E attached
        rx_xmit(data, 12);
     } else {
        // RX11/RXV11 attached
        rx_xmit(data, 8);
    }

    return;
}



//
// setup error/status word
//
static uint16_t rx_init_es (void)
{
    uint16_t es;

    // clear all bits except init done
    es = (rx.es & RXES_ID);
    // insert drive type (RX28 only w/ RX02)
    if (rx.type == RX_TYPE_RX02 && rx_tst_12b()) es |= RXES_RX02;
    // insert unit selected
    if (rx.unit == 1 && rx_tst_dma()) es |= RXES_UNITSEL;
    // insert drive ready for that unit
    if (rx.drv[rx.unit].rdy) es |= RXES_DRVRDY;
    // insert density for that unit
    if (rx.drv[rx.unit].den == RX_DEN_DD) es |= RXES_DRVDEN;

    return es;
}

// setup error/status with flags:  RXES_CRC, RXES_DENERR, RXES_DELDATA, RXES_WCOVF are data flag options

static uint16_t rx_init_es (uint16_t data)
{
    return rx_init_es() | data;
}



// PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC



//
// initialize the RX subsystem
//
//    flag:  TRUE for powerup init; FALSE for INIT pulse seen
//
void rx_initialize (uint8_t flag)
{
    uint8_t i;

    // led status
    led_state(red, on);
    led_state(green, off);
    led_state(yellow, off);

    // setup at power up ... required first time only
    if (flag) {

        // setup pins
        pinMode(PIN_CTLR_DMA_MODE_L, INPUT);
        pinMode(PIN_CTLR_DATAI_H, INPUT);
        pinMode(PIN_CTLR_12BIT_H, INPUT);
        pinMode(PIN_CTLR_INIT_H, INPUT); // interrupt
        pinMode(PIN_CTLR_RUN_H, INPUT);
        //
        pinMode(PIN_CTLR_TR_RQST_H, OUTPUT);
        pinMode(PIN_CTLR_DATAO_H, OUTPUT);
        pinMode(PIN_CTLR_SHIFT_H, OUTPUT);
        pinMode(PIN_CTLR_ERROR_H, OUTPUT);
        pinMode(PIN_CTLR_ACLO_H, OUTPUT);
        pinMode(PIN_CTLR_DONE_H, OUTPUT);
        pinMode(PIN_CTLR_OUT_H, OUTPUT);

        // setup RX INIT interrupt
        rx_init_seen = 0;
        attachInterrupt(digitalPinToInterrupt(PIN_CTLR_INIT_H), rx_intr_init, RISING);

    }

    // setup control outputs
    rx_clr_out();
    rx_clr_aclo();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();
    rx_clr_request();

    // toggle ACLO at initial reset
    if (flag) { rx_set_aclo(); delay(100); rx_clr_aclo(); }

    // wait for INIT to go away
    uint32_t start = millis(); // current time, in ms
    uint32_t expire = 10000UL; // 10 sec delay
    if (debug) debug->printf("RX: waiting for INIT to clear ... t=%lums\n", millis());
    while ((rx_init_seen || rx_tst_init()) && millis()-start <= expire) { rx_init_seen = 0; } 
    if (debug) debug->printf("RX: INIT has %scleared t=%lums\n", rx_tst_init() ? "NOT " : "", millis());

    // setup drive data structures
    rx.type = rx_type;
    rx.fcn.code = 8;
    rx.fcn.name = "INIT";
    rx.den = rx.type == RX_TYPE_RX02 ? RX_DEN_DD : RX_DEN_SD;
    rx.cs = 0;
    rx.es = 0;
    rx.ta = 0;
    rx.sa = 0;
    rx.wc = 0;
    rx.bc = 0;
    rx.len = 0;
    rx.pos = 0;
    rx.unit = 0;
    rx.ecode = 0;

    // setup drive state
    for (i = 0; i < RX_NUNITS; ++i) {
        if (flag) sprintf(rx.drv[i].name, "RX%d.DSK", i);
        rx_set_unit_file(i, NULL);
    }

    // clear sector buffer
    memset(rx.buffer, 0x00, sizeof(rx.buffer));

    // indicate SD card access in progress
    led_state(yellow, on);

    // copy drive 0 boot sector into the buffer
    rx.drv[0].ta = rx.ta = 1;
    rx.drv[0].sa = rx.sa = 1;
    rx.drv[0].len = rx.len = rx_sec_size(rx.drv[0].den);
    rx.drv[0].pos = rx.pos = (rx.ta*RX_NSECS + (rx.sa - 1)) * rx.len;
    sd_read_bytes(rx.drv[0].name, rx.pos, rx.buffer, rx.len);

    // indicate SD card access complete
    led_state(yellow, off);

    // init time delay
    rx_timing(RX_INIT_TIME);

    // setup error/status bits
    rx.es = rx_init_es(RXES_ID);

    // transmit status on INIT
    rx_xmit_es(rx.es);

    // led status
    led_state(red, off);

    return;
}



//
// setup the file for rx drive unit
//
void rx_set_unit_file (uint8_t unit, char *name)
{
    uint32_t size;

    // setup file name if provided
    if (name) strcpy(rx.drv[unit].name, name);

    // get size of (existing) file, check it for SD or DD or none
    size = sd_get_file_size(rx.drv[unit].name);
    if (!(rx.type == RX_TYPE_RX01 && size == rx_dsk_size(RX_DEN_SD) ||
          rx.type == RX_TYPE_RX02 && (size == rx_dsk_size(RX_DEN_SD) || size == rx_dsk_size(RX_DEN_DD)))
        ) sd_set_file_size(rx.drv[unit].name, rx.type == RX_TYPE_RX01 ? rx_dsk_size(RX_DEN_SD) : rx_dsk_size(RX_DEN_DD));

    // set initial state from common block
    rx.drv[unit].ta = rx.ta;
    rx.drv[unit].sa = rx.sa;
    rx.drv[unit].len = rx.len;
    rx.drv[unit].pos = rx.pos;

    // set drive ready if file exists and is right size (SD or DD)
    rx.drv[unit].rdy = sd_get_file_size(rx.drv[unit].name) > 0;

    // set drive density based on file size
    rx.drv[unit].den = sd_get_file_size(rx.drv[unit].name) == rx_dsk_size(RX_DEN_DD) ? RX_DEN_DD : RX_DEN_SD;

    // zap the deleted data state to normal
    memset(rx.drv[unit].dd, RX_NORMAL_DATA, sizeof(rx.drv[unit].dd));

    return;
}



//
// setup the RX02 subsystem debug port
//
void rx_debug (HardwareSerial *serialPort)
{
    // some status
    if (debug != NULL && serialPort == NULL) debug->printf("RX: debug disabled\n");
    if (debug == NULL && serialPort != NULL) serialPort->printf("RX: debug enabled\n");

    // set debug serial port (or not)
    debug = serialPort;

    // and done
    return;
}



//
// print RX emulator state
//
void rx_print_state (HardwareSerial *prt)
{
    uint8_t i;
    char den[] = { 'S', 'D', 'Q' };

    // dump to user supplied port if not null
    if (prt == NULL) prt = debug;
    // else try debug port, else return
    if (prt == NULL) return;

    prt->printf("\n--- RX Emulator State Dump ---\n\n");
    prt->printf("    i/f width = %d.\n",   rx_get_bits());
    prt->printf("     i/f mode = %s\n",    rx_tst_dma() ? "DMA" : "PIO");
    prt->printf("     i/f init = %o\n",    rx_tst_init());
    prt->printf("      i/f run = %o\n",    rx_tst_run());
    prt->printf("\n");
    prt->printf("        rx.cs = %06o\n",  rx.cs);
    prt->printf("        rx.es = %04o\n",  rx.es);
    prt->printf("        rx.wc = %03o\n",  rx.wc);
    prt->printf("        rx.bc = %03o\n",  rx.bc);
    prt->printf("        rx.ta = %03o\n",  rx.ta);
    prt->printf("        rx.sa = %03o\n",  rx.sa);
    prt->printf("       rx.pos = %lu.\n",  rx.pos);
    prt->printf("       rx.len = %u.\n",   rx.len);
    prt->printf("       rx.den = %cD\n",   den[rx.den]);
    prt->printf("      rx.unit = %o\n",    rx.unit);
    prt->printf("      rx.type = RX0%d\n", rx.type+1);
    prt->printf("     rx.ecode = %04o\n",  rx.ecode);
    prt->printf("  rx.fcn.code = %o\n",    rx.fcn.code);
    prt->printf("  rx.fcn.name = %s\n",    rx.fcn.name);
    for (i = 0; i < RX_NUNITS; ++i) {
        prt->printf("\n");
        prt->printf("    rx.drv[%d].name = '%s'\n", i,  rx.drv[i].name);
        prt->printf("     rx.drv[%d].rdy = %c\n", i, rx.drv[i].rdy ? 'Y' : 'N');
        prt->printf("     rx.drv[%d].den = %cD\n", i, den[rx.drv[i].den]);
        prt->printf("      rx.drv[%d].ta = %03o\n", i, rx.drv[i].ta);
        prt->printf("      rx.drv[%d].sa = %03o\n", i, rx.drv[i].sa);
        prt->printf("     rx.drv[%d].pos = %lu.\n", i, rx.drv[i].pos);
        prt->printf("     rx.drv[%d].len = %u.\n", i, rx.drv[i].len);
    }

    return;
}



//
// execute an RX function
//
void rx_function (void)
{
    uint16_t i, j;
    uint16_t value;

    // setup for a new request
    rx_clr_request();
    rx_set_done();

    // setup INIT seen callback
    if (setjmp(rx_init_env)) {
        // return to here on longjmp(rx_init_env,N);
        rx_init_seen = 0;
        rx_initialize(false);
        return;
    }

    // wait for RUN or INIT
    uint8_t stat = rx_wait_run_or_init(250);
    // return if timeout with no RUN or INIT
    if (stat == RX_SAW_NONE) return;
    // fall thru if RUN seen ...

    // RUN seen, process command

    // receive a command word: 12b on RX211 or RX8E/28 in 12b mode; 8b otherwise
    rx.cs = rx_recv(rx_tst_dma() || rx_tst_12b() ? 12 : 8);
    if (debug) debug->printf("RX: cmd=%04o\n", rx.cs);

    // led status
    led_state(red, off);
    led_state(green, on);
    led_state(yellow, off);

    // separate out the function field
    rx.fcn.code = (rx.cs & RXCS_FUNCTION)>>1;
    rx.fcn.name = fcn_name_list[rx.fcn.code];

    // separate out the unit number in the command
    rx.unit = (rx.cs & RXCS_UNITSEL) ? 1 : 0;

    // separate out the density flag in the command
    rx.den = (rx.cs & RXCS_DENSEL) ? RX_DEN_DD : RX_DEN_SD;

    // setup error/status bits
    rx.es = rx_init_es();

    // initial error code: success!
    rx.ecode = RXERR_SUCCESS;

    // print command
    if (debug) debug->printf("RX: %s unit=%o den=%o\n", rx.fcn.name, rx.unit, rx.den);

    // decode command function
    switch (rx.fcn.code) {

    // === buffer fill or empty ===
    //
    // DMA: command(12), wordcount(8), N*databytes(8)
    // PIO: command(12), N*databytes(8 or 12)
    //
    case RXFCN_FILL:
    case RXFCN_EMPTY:

        // compute word count
        if (rx_tst_dma()) {
            // if DMA mode, receive word count from interface
            rx.wc = rx_recv_hs(8);
            // receive bus address
            // rx.ba = rx_recv_hs(16); // done in RX211 hardware
        } else {
            // if PIO mode, compute word count from full sector size
            rx.wc = rx_sec_size(rx.den) >> (rx_tst_8b() ? 1 : 2);
        }
        if (debug) debug->printf("RX: %s wc=%03o\n", rx.fcn.name, rx.wc);

        // transform word count to byte count for transfer
        rx.bc = 2*rx.wc;

        // check for word count overflow
        if (rx.bc > rx_sec_size(rx.drv[rx.unit].den)) {
            if (debug) debug->printf("RX: %s wc=%03o WCOVF\n", rx.fcn.name, rx.wc);
            rx.ecode = RXERR_WCOVF;
            rx.es |= RXES_WCOVF;
            goto error;
        }

        // transfer data words in/out
        if (rx.fcn.code == RXFCN_EMPTY) {
            // empty buffer (RX controller buffer to host interface)
            for (i = 0; i < rx.bc; ++i) {
                if (rx_tst_8b()) {
                    // extract 8b values from buffer to send as 8b
                    value = rx.buffer[i];
                    rx_xmit_hs(value, 8);
                    if (debug) debug->printf("RX: %s d[%03o]=%03o\n", rx.fcn.name, i, value);
                } else {
                    // 12b values are packed into the first 2/3 of the sector; read 2 8b entries and build 12b value
                    j = 3*i/2;
                    if (i & 1) {
                        // odd byte
                        value = ((rx.buffer[j+0] & 017) << 8) | rx.buffer[j+1];
                    } else {
                        // even byte
                        value = (rx.buffer[j+0] << 4) | ((rx.buffer[j+1] >> 4) & 017);
                    }
                    rx_xmit_hs(value, 12);
                    if (debug) debug->printf("RX: %s d[%03o]=%04o\n", rx.fcn.name, i, value);
                }
             }
        } else {
            // fill buffer (host interface to RX controller buffer)
            for (i = 0; i < rx.bc; ++i) {
                if (rx_tst_8b()) {
                    // 8b values get stuffed into 8b buffer directly
                    value = rx_recv_hs(8);
                    rx.buffer[i] = value;
                    if (debug) debug->printf("RX: %s d[%03o]=%03o\n", rx.fcn.name, i, value);
                } else {
                    // 12b values get split into 4b/8b and merged into two 8b entries
                    value = rx_recv_hs(12);
                    j = 3*i/2;
                    if (i & 1) {
                        // odd byte
                        rx.buffer[j+0] |= (value >> 8) & 017;
                        rx.buffer[j+1] = value;
                    } else {
                        // even byte
                        rx.buffer[j+0] = value >> 4;
                        rx.buffer[j+1] = (value & 017) << 4;
                    }
                    if (debug) debug->printf("RX: %s d[%03o]=%04o\n", rx.fcn.name, i, value);
                }
            }
            // zero fill any unwritten bytes in the buffer
            for (i = rx.bc, j = rx_sec_size(rx.den); i < j; ++i) rx.buffer[i] = 0;
        }

        // indicate transfer is complete
        rx.wc = 0;

        break;

    // === read/write sector ===
    //
    // command(12), sector(8 or 12), track(8 or 12)
    //
    case RXFCN_RDSECT:
    case RXFCN_WRSECT:  
    case RXFCN_WRDDSECT:

        // first word is sector address
        rx.sa = rx_recv_hs() & 037;
        if (debug) debug->printf("RX: %s sa=%d.\n", rx.fcn.name, rx.sa);

        // second word is track address
        rx.ta = rx_recv_hs() & 0177;
        if (debug) debug->printf("RX: %s ta=%d.\n", rx.fcn.name, rx.ta);

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, clear drive ready bit and error
            rx.es &= ~RXES_DRVRDY;
            goto error;
        }

        // check track access is valid
        if (rx.ta >= RX_NTRKS) {
            rx.ecode = RXERR_TRKERR;
            goto error;
        }

        // check density matches, error if does not
        if (rx.type != RX_TYPE_RX01 && rx.den != rx.drv[rx.unit].den) {
            rx.es |= RXES_DENERR;
            rx.ecode = RXERR_DENERR;
            goto error;
        }

        // check sector address is valid
        if (rx.sa < 1 || rx.sa > RX_NSECS) {
            rx.ecode = RXERR_SECFAIL;
            goto error;
        }

        // compute length of transfer (sector size)
        rx.len = rx_sec_size(rx.drv[rx.unit].den);
        // compute byte offset into disk image
        rx.pos = (rx.ta*RX_NSECS + (rx.sa-1)) * rx.len;
        // print details if in debug mode
        if (debug) debug->printf("RX: %s pos=%lu. len=%u.\n", rx.fcn.name, rx.pos, rx.len);

        // bail on INIT seen
        if (rx_init_seen) { longjmp(rx_init_env, 20); }

        // indicate SD card access in progress
        led_state(yellow, on);

        // do the read or write
        if (rx.fcn.code == RXFCN_RDSECT) {
            // do a read; copy data into buffer from diskimage@offset
            value = sd_read_bytes(rx.drv[rx.unit].name, rx.pos, rx.buffer, rx.len);
        } else {
            // do a write
            if (rx.fcn.code == RXFCN_WRDDSECT) {
                bitWrite(rx.drv[rx.unit].dd[rx.ta], rx.sa, RX_DELETED_DATA);
            } else {
                bitWrite(rx.drv[rx.unit].dd[rx.ta], rx.sa, RX_NORMAL_DATA);
            }
            // copy data from rx.buffer[] to diskimage@offset
            value = sd_write_bytes(rx.drv[rx.unit].name, rx.pos, rx.buffer, rx.len);
        }
        if (bitRead(rx.drv[rx.unit].dd[rx.ta], rx.sa) == RX_DELETED_DATA) rx.es |= RXES_DELDATA;

        // SD card access complete
        led_state(yellow, off);

        // simulate timing
        rx_timing(rx_seek_time(abs(rx.ta - rx.drv[rx.unit].ta)));

        // update state info for drive
        rx.drv[rx.unit].ta = rx.ta;
        rx.drv[rx.unit].sa = rx.sa;
        rx.drv[rx.unit].pos = rx.pos;
        rx.drv[rx.unit].len = rx.len;

        // check for read/write error
        if (value != rx.len) {
            rx.ecode = RXERR_IDMFAIL;
            goto error;
        }

        break;

    // === set media density ===
    //
    // command(12), key(8 or 12)
    //
    case RXFCN_SETMEDIA:

        // this function is a NOP for the RX01
        if (rx.type == RX_TYPE_RX01) break;

        // first word must be the magic key 'I'
        value = rx_recv_hs();
        // check key for expected value
        if (value != 'I') {
            rx.ecode = RXERR_KEYERR;
            goto error;
        }

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, clear drive ready bit and error
            rx.es &= ~RXES_DRVRDY;
            goto error;
        }

        // simulate timing
        rx_timing(RX_SETMEDIA_TIME);

        // bail on INIT seen
        if (rx_init_seen) { longjmp(rx_init_env, 21); }

        // indicate SD card access in progress
        led_state(yellow, on);

        // change density to requested value
        sd_set_file_size(rx.drv[rx.unit].name, rx_dsk_size(rx.den));
        rx.drv[rx.unit].den = rx.den;

        // indicate SD card access complete
        led_state(yellow, off);

        break;

    // === read status ===
    //
    // command(12)
    //
    case RXFCN_RDSTAT:

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, clear drive ready bit and error
            rx.es &= ~RXES_DRVRDY;
            goto error;
        }

        // simulate timing
        rx_timing(RX_RDSTAT_TIME);

        // check that density matches, error if does not
        if (rx.type != RX_TYPE_RX01 && rx.den != rx.drv[rx.unit].den) {
            rx.es |= RXES_DENERR;
            rx.ecode = RXERR_DENERR;
            goto error;
        }

        break;

    // === read error code ===
    //
    // command(12)
    //
    case RXFCN_RDERROR:

        // simulate timing
        rx_timing(RX_RDERROR_TIME);

        // only executes on RX211/RXV21; return four 16b status words
        if (rx_tst_dma()) {

            // receive bus address
            // rx.ba = rx_recv_hs(16); // done in RX211 hardware

            // generate status byte
            value = (rx.unit == 1               ? (1<<7) : 0)
                  | (rx.drv[1].den == RX_DEN_DD ? (1<<6) : 0)
                  | (rx.drv[rx.unit].rdy        ? (1<<5) : 0)
                  | (rx.drv[0].den == RX_DEN_DD ? (1<<4) : 0)
                  | (rx.den == RX_DEN_DD        ? (1<<0) : 0);

            // word 1
            rx_xmit_hs(rx.ecode, 8);
            rx_xmit_hs(rx.wc, 8);

            // word 2
            rx_xmit_hs(rx.drv[0].ta, 8);
            rx_xmit_hs(rx.drv[1].ta, 8);

            // word 3
            rx_xmit_hs(rx.ta, 8);
            rx_xmit_hs(rx.sa, 8);

            // word 4
            rx_xmit_hs(value, 8);
            rx_xmit_hs(rx.drv[rx.unit].ta, 8);

        }

        break;

    } // switch (rx.fcn.num)

done:
    rx_xmit_es(rx.es);
    led_state(green, off);
    return;

error:
    rx_set_error();
    led_state(red, on);
    goto done;
}



// the end
 
Last edited:
Thanks for posting this Don. Your shield looks great. We've gotten shields back for our Arduino version. I'm trying to do a little bit each day and hopefully will be able to add my findings soon.

Just for reference here is my current Arduino driver code for the RX02 interface, originally based on the CHD RXM, but with lots of optimization tweaks for the Arduino.

At some point I'll probably upload my entire RX02 emulator project to my GITHUB area alongside TU58EM et al, but here is a 'first look' for anyone interested in comparing vs the Bela implementation.

rx02_driver.h prototype file:
Code:
//
// rx02_driver - Simple RX02 driver external interface
//
// (C) 2013 Don North <ak6dn_at_mindspring_dot_com>
//
// 21 Oct 2015 - donorth - Initial code
// 27 Mar 2016 - donorth- Ported to Arduino from CCS/Microchip
//

#ifndef rx02_driver_h
#define rx02_driver_h



//
// public prototypes
//
void rx_initialize (uint8_t flag);
void rx_debug (HardwareSerial *serialPort);
void rx_print_state (HardwareSerial *serialPort);
void rx_set_unit_file (uint8_t unit, char *name);
void rx_function (void);



#endif // rx02_driver_h

// the end

rx02_driver.cpp implementation:
Code:
//
// rx02_driver - Simple RX02 driver
//
// (C) 2013 Don North <ak6dn_at_mindspring_dot_com>
//
// 21 Oct 2013 - donorth - Initial code
// 27 Mar 2016 - donorth- Ported to Arduino from CCS/Microchip
//



//
// includes
//
#include "my_project.h"
#include "led_driver.h"
#include "rx02_driver.h"
#include "sdcard_driver.h"



//
// definitions
//

// options

#define USE_BIT_BUILTINS 1 // use bit builtins BitRead() etc vs use logical bit shift/mask operations

// RXCS control/status bit definitions

//efine RXCS_GO        (1<<0)    // GO bit                       (reference only; not used in drive)
#define RXCS_FUNCTION  (7<<1)    // function bitfield                            
#define RXCS_UNITSEL   (1<<4)    // unit select
//efine RXCS_DONE      (1<<5)    // function complete            (reference only; not used in drive)
//efine RXCS_INTENB    (1<<6)    // interrupt enable             (reference only; not used in drive)
//efine RXCS_8BIT      (1<<6)    // 8b mode                      (RX8E/RX28 ONLY)
//efine RXCS_TR        (1<<7)    // transfer request             (reference only; not used in drive)
//efine RXCS_MAINT     (1<<7)    // maintenance mode             (RX8E/RX28 ONLY)
#define RXCS_DENSEL    (1<<8)    // density select
//efine RXCS_HEADSEL   (1<<9)    // head select
//efine RXCS_RX02     (1<<11)    // RX02 interface               (reference only; not used in drive)
//efine RXCS_EXTADDR  (3<<12)    // upper bits of phys address   (reference only; not used in drive)
//efine RXCS_INIT     (1<<14)    // initialize RX                (reference only; not used in drive)
//efine RXCS_ERROR    (1<<15)    // error flag                   (reference only; not used in drive)

#define RXFCN_FILL       (0)      // fill buffer
#define RXFCN_EMPTY      (1)      // empty buffer
#define RXFCN_WRSECT     (2)      // write sector
#define RXFCN_RDSECT     (3)      // read sector
#define RXFCN_SETMEDIA   (4)      // set media density
#define RXFCN_RDSTAT     (5)      // read status
#define RXFCN_WRDDSECT   (6)      // write deleted data sector
#define RXFCN_RDERROR    (7)      // read error code

// RXES status bit definitions

//efine RXES_CRC       (1<<0)    // CRC aka READ error
//efine RXES_SIDE      (1<<1)    // side                         (RX211/RX28 ONLY)
//efine RXES_PERR      (1<<1)    // parity error                 (RX11/RX8E w/RX01 ONLY)
#define RXES_ID        (1<<2)    // controller init done
#define RXES_RX02      (1<<3)    // set for RX02 drive           (RX28 w/ RX02 ONLY)
//efine RXES_ACLO      (1<<3)    // set for AC low               (RX211 w/RX02 ONLY)
//efine RXES_WPERR     (1<<3)    // set for write to WP drive    (RX11/RX8E w/RX01 ONLY)
#define RXES_DENERR    (1<<4)    // density error
#define RXES_DRVDEN    (1<<5)    // diskette double density
#define RXES_DELDATA   (1<<6)    // deleted data detected 
#define RXES_DRVRDY    (1<<7)    // drive ready
#define RXES_UNITSEL   (1<<8)    // unit selected                (RX211 w/RX02 ONLY)
#define RXES_WCOVF    (1<<10)    // word count overflow          (RX211 w/RX02 ONLY)
//efine RXES_NXM      (1<<11)    // nonexistent memory           (RX211 w/RX02 reference only; not used in drive)

// RX error codes

#define RXERR_SUCCESS   0000   // success, no error
//efine RXERR_DR0INIT   0010    // drive 0 failed to init
//efine RXERR_DR1INIT   0020    // drive 1 failed to init
//efine RXERR_STEPHOME  0030   // found home when stepping for init (RX01 ONLY)
#define RXERR_TRKERR    0040    // access to track > 76
#define RXERR_TRKFAIL   0050    // track not found
//efine RXERR_SELFDIAG  0060   // self diagnostic fail (RX01 ONLY)
#define RXERR_SECFAIL   0070    // sector not found
//efine RXERR_WRITEWP   0100   // write to a WP drive (RX01 ONLY)
//efine RXERR_SEPFAIL   0110    // no SEP clock in 40us
//efine RXERR_PREFAIL   0120    // preamble not found
#define RXERR_IDMFAIL   0130    // ID mark not found
//efine RXERR_CRCHEAD   0140   // CRC error on a header (RX01 ONLY)
//efine RXERR_TRKCMP    0150    // header track miscompare
//efine RXERR_IDMTRYS   0160    // too many tries for IDAM
//efine RXERR_DAMFAIL   0170    // data AM not found
//efine RXERR_CRCERR    0200    // CRC error on read
//efine RXERR_PERR      0210   // parity error on word from i/f to controller (RX01 ONLY)
//efine RXERR_RWFAIL    0220    // r/w failed maint test (RX02 ONLY)
#define RXERR_WCOVF     0230    // word count overflow (RX02 ONLY)
#define RXERR_DENERR    0240    // density error (RX02 ONLY)
#define RXERR_KEYERR    0250    // wrong key word for set density (RX02 ONLY)

// macros

// rx interface outputs set/clr

#if USE_BIT_BUILTINS
#define rx_set_done(xxx)     bitSet(PORTA,6)    /* digitalWrite(PIN_CTLR_DONE_H,HIGH) */
#define rx_clr_done(xxx)     bitClear(PORTA,6)  /* digitalWrite(PIN_CTLR_DONE_H,LOW) */
#define rx_set_error(xxx)    bitSet(PORTA,7)    /* digitalWrite(PIN_CTLR_ERROR_H,HIGH) */
#define rx_clr_error(xxx)    bitClear(PORTA,7)  /* digitalWrite(PIN_CTLR_ERROR_H,LOW) */
#define rx_set_aclo(xxx)     bitSet(PORTC,1)    /* digitalWrite(PIN_CTLR_ACLO_H,HIGH) */
#define rx_clr_aclo(xxx)     bitClear(PORTC,1)  /* digitalWrite(PIN_CTLR_ACLO_H,LOW) */
#define rx_set_shift(xxx)    bitSet(PORTC,2)    /* digitalWrite(PIN_CTLR_SHIFT_H,HIGH) */
#define rx_clr_shift(xxx)    bitClear(PORTC,2)  /* digitalWrite(PIN_CTLR_SHIFT_H,LOW) */
#define rx_set_out(xxx)      bitSet(PORTC,3)    /* digitalWrite(PIN_CTLR_OUT_H,HIGH) */
#define rx_clr_out(xxx)      bitClear(PORTC,3)  /* digitalWrite(PIN_CTLR_OUT_H,LOW) */
#define rx_set_datao(xxx)    bitSet(PORTC,5)    /* digitalWrite(PIN_CTLR_DATAO_H,HIGH) */
#define rx_clr_datao(xxx)    bitClear(PORTC,5)  /* digitalWrite(PIN_CTLR_DATAO_H,LOW) */
#define rx_set_request(xxx)  bitSet(PORTC,6)    /* digitalWrite(PIN_CTLR_TR_RQST_H,HIGH) */
#define rx_clr_request(xxx)  bitClear(PORTC,6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,LOW) */
// rx interface inputs test
#define rx_tst_pio(xxx)      bitRead(PINC,0)    /*  digitalRead(PIN_CTLR_DMA_MODE_L) */
#define rx_tst_dma(xxx)      (rx_tst_pio()?0:1) /* !digitalRead(PIN_CTLR_DMA_MODE_L) */
#define rx_tst_datai(xxx)    bitRead(PINC,4)    /*  digitalRead(PIN_CTLR_DATAI_H) */
#define rx_tst_12b(xxx)      bitRead(PINC,7)    /*  digitalRead(PIN_CTLR_12BIT_H) */
#define rx_tst_8b(xxx)       (rx_tst_12b()?0:1) /* !digitalRead(PIN_CTLR_12BIT_H) */
#define rx_tst_run(xxx)      bitRead(PINE,4)    /*  digitalRead(PIN_CTLR_RUN_H)) */
#define rx_tst_init(xxx)     bitRead(PINE,5)    /*  digitalRead(PIN_CTLR_INIT_H) */
#else
#define rx_set_done(xxx)     PORTA |=  (1<<6)  /* digitalWrite(PIN_CTLR_DONE_H,HIGH) */
#define rx_clr_done(xxx)     PORTA &= ~(1<<6)  /* digitalWrite(PIN_CTLR_DONE_H,LOW) */
#define rx_set_error(xxx)    PORTA |=  (1<<7)  /* digitalWrite(PIN_CTLR_ERROR_H,HIGH) */
#define rx_clr_error(xxx)    PORTA &= ~(1<<7)  /* digitalWrite(PIN_CTLR_ERROR_H,LOW) */
#define rx_set_aclo(xxx)     PORTC |=  (1<<1)  /* digitalWrite(PIN_CTLR_ACLO_H,HIGH) */
#define rx_clr_aclo(xxx)     PORTC &= ~(1<<1)  /* digitalWrite(PIN_CTLR_ACLO_H,LOW) */
#define rx_set_shift(xxx)    PORTC |=  (1<<2)  /* digitalWrite(PIN_CTLR_SHIFT_H,HIGH) */
#define rx_clr_shift(xxx)    PORTC &= ~(1<<2)  /* digitalWrite(PIN_CTLR_SHIFT_H,LOW) */
#define rx_set_out(xxx)      PORTC |=  (1<<3)  /* digitalWrite(PIN_CTLR_OUT_H,HIGH) */
#define rx_clr_out(xxx)      PORTC &= ~(1<<3)  /* digitalWrite(PIN_CTLR_OUT_H,LOW) */
#define rx_set_datao(xxx)    PORTC |=  (1<<5)  /* digitalWrite(PIN_CTLR_DATAO_H,HIGH) */
#define rx_clr_datao(xxx)    PORTC &= ~(1<<5)  /* digitalWrite(PIN_CTLR_DATAO_H,LOW) */
#define rx_set_request(xxx)  PORTC |=  (1<<6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,HIGH) */
#define rx_clr_request(xxx)  PORTC &= ~(1<<6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,LOW) */
// rx interface inputs test
#define rx_tst_pio(xxx)     ((PINC & (1<<0)) ? 1 : 0) /* (digitalRead(PIN_CTLR_DMA_MODE_L)==HIGH) */
#define rx_tst_dma(xxx)     ((PINC & (1<<0)) ? 0 : 1) /* (digitalRead(PIN_CTLR_DMA_MODE_L)==LOW) */
#define rx_tst_datai(xxx)   ((PINC & (1<<4)) ? 1 : 0) /* digitalRead(PIN_CTLR_DATAI_H) */
#define rx_tst_12b(xxx)     ((PINC & (1<<7)) ? 1 : 0) /* (digitalRead(PIN_CTLR_12BIT_H)==HIGH) */
#define rx_tst_8b(xxx)      ((PINC & (1<<7)) ? 0 : 1) /* (digitalRead(PIN_CTLR_12BIT_H)==LOW) */
#define rx_tst_run(xxx)     ((PINE & (1<<4)) ? 1 : 0) /* (digitalRead(PIN_CTLR_RUN_H)==HIGH) */
#define rx_tst_init(xxx)    ((PINE & (1<<5)) ? 1 : 0) /* (digitalRead(PIN_CTLR_INIT_H)==HIGH) */
#endif

// INIT/RUN status

#define RX_SAW_NONE     0               // saw neither RUN or INIT
#define RX_SAW_RUN      1               // saw RUN and not INIT

// emulation type

#define RX_TYPE_RX01    0               // RX02 in RX01 mode
#define RX_TYPE_RX02    1               // RX02 in native mode

// drive density

#define RX_DEN_SD       0                // single density mode
#define RX_DEN_DD       1                // double density mode

// data type

#define RX_NORMAL_DATA  0               // normal data
#define RX_DELETED_DATA 1               // deleted data

// disk definitions

#define RX_NTRKS        77L             // number of tracks per disk
#define RX_NSECS        26L             // number of sectors per track
#define RX_NBPS         128L            // bytes per sector, single density
#define RX_NUNITS       2               // number if units

#define RX_BUFFER_SIZE  (2*RX_NBPS)     // maximum size sector buffer for double density

// convenience macros

#define rx_sec_size(den)     (RX_NBPS<<(den))             // bytes per sector at density
#define rx_trk_size(den)     (RX_NSECS*rx_sec_size(den))  // bytes per track at density
#define rx_dsk_size(den)     (RX_NTRKS*rx_trk_size(den))  // bytes per disk at density

#define rx_get_bits(xxx)     (rx_tst_12b() ? 12 : 8)      // number of bits signaled by interface



//
// timing characteristics
//
#define RX02_ACTUAL_SPEED 0

// actual hardware timing
#if (RX02_ACTUAL_SPEED == 1)
#define RX_RDSTAT_TIME      (250)     // read status function
#define RX_INIT_TIME        (500)     // initialize floppy drive
#define RX_STEP_TIME         (10)     // track step time
#define RX_SETTLE_TIME       (20)     // seek settling time
#define RX_SEC_TIME          (20)     // sector access time
#define RX_RDERROR_TIME       (1)     // read error registers
#define RX_SETMEDIA_TIME  (10000)     // set media density
#endif // (RX02_ACTUAL_SPEED == 1)

// as fast as it can go
#if (RX02_ACTUAL_SPEED == 0)
#define RX_RDSTAT_TIME        (0)     // read status function
#define RX_INIT_TIME          (0)     // initialize floppy drive
#define RX_STEP_TIME          (0)     // track step time
#define RX_SETTLE_TIME        (0)     // seek settling time
#define RX_SEC_TIME           (0)     // sector access time
#define RX_RDERROR_TIME       (0)     // read error registers
#define RX_SETMEDIA_TIME      (0)     // set media density
#endif // (RX02_ACTUAL_SPEED == 0)

// compute seek time based on delta track amount
#define rx_seek_time(d) (RX_SEC_TIME + ((abs(d) == 0) ? 0 : RX_SETTLE_TIME + abs(d)*RX_STEP_TIME))



// PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE



volatile static uint8_t rx_init_seen; // set when RX INIT asserted (rising edge)

static jmp_buf rx_init_env; // environment to invoke when INIT is asserted (ie, reset/error condition)

static char fcn_name_list[][10] = { "FILL", "EMPTY", "WRSECT", "RDSECT", "SETMEDIA", "RDSTAT", "WRDDSECT", "RDERROR" };

static uint8_t rx_type = RX_TYPE_RX02; // default drive emulation, can be overwritten

struct drv_t {
    uint32_t dd[RX_NTRKS];  // normal or deleted data per track per sector (1 bit per sector 0..31)
    char     name[32];      // associated file name
    uint8_t  rdy;           // drive ready
    uint8_t  den;           // density
    uint8_t  ta;            // track address
    uint8_t  sa;            // sector address
    uint16_t len;           // read/write length
    uint32_t pos;           // read/write position
};

static struct rx_t {
    uint8_t  type;     // emulation type: RX01, RX02
    uint16_t cs;       // control/status image
    uint16_t es;       // error and status
    uint16_t bc;       // byte count
    uint8_t  wc;       // word count
    uint8_t  ecode;    // error code
    uint8_t  unit;     // active unit number
    uint8_t  den;      // density selected, DD or SD
    uint8_t  ta;       // track address
    uint8_t  sa;       // sector address
    uint16_t len;      // read/write length
    uint32_t pos;      // read/write position
    struct {
        uint8_t code;  // function selected (numeric 0..7)
        char *  name;  // function selected, ascii name id
    } fcn;
    struct drv_t drv[RX_NUNITS]; // drive specific parameters
    uint8_t buffer[RX_BUFFER_SIZE]; // sector buffer
} rx;

static HardwareSerial *debug = NULL; // debug serial output



//
// RX02 INIT assertion interrupt
//
static void rx_intr_init (void)
{
    rx_init_seen = 1;
    return;
}



//
// RX time delay routine
//
static void rx_timing (uint16_t ms_delay)
{
    const uint16_t ms_amount = 5;

    // check for delay requested
    if (ms_delay > 0) {

        // delay in 5ms increments, checking for INIT
        while (ms_delay > ms_amount) {
            if (rx_init_seen) { longjmp(rx_init_env, 10); }
            delay(ms_amount);
            ms_delay -= ms_amount;
        }

        // final small delay less than 5ms
        if (rx_init_seen) { longjmp(rx_init_env, 10); }
        delay(ms_delay);

    }

    // all done
    if (rx_init_seen) { longjmp(rx_init_env, 10); }
    return;
}



//
// wait until the RX_RUN or RX_INIT signal is asserted, or timeout occurs
//
static uint8_t rx_wait_run_or_init (uint32_t expire)
{
    uint32_t start = millis();

    do {
        if (rx_init_seen) { longjmp(rx_init_env, 1); }
        if (rx_tst_run()) { return RX_SAW_RUN; }
    } while (millis()-start <= expire);

    // count timed out with neither RUN or INIT
    return RX_SAW_NONE;
}

// wait forever until the RX_RUN or RX_INIT signal is asserted

static uint8_t rx_wait_run_or_init (void)
{
    do {
        if (rx_init_seen) { longjmp(rx_init_env, 2); }
        if (rx_tst_run()) { return RX_SAW_RUN; }
    } while (TRUE);
}



//
// receive a 12/8 bit word from the RX interface, msb first
//

#define _rx_recv_hs(xxx,yyy) { if (rx_tst_datai()) { delay_1c(); (xxx) |= (1<<(yyy)); } else { (xxx) &= ~(1<<(yyy)); delay_2c(); } delay_1c(); rx_set_shift(); delay_1c(); rx_clr_shift(); }

static uint16_t rx_recv12_hs (uint8_t handshake)
{
    uint16_t data = 0;

    rx_clr_out();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    noInterrupts();

    _rx_recv_hs(data,11);
    _rx_recv_hs(data,10);
    _rx_recv_hs(data,9);
    _rx_recv_hs(data,8);
    _rx_recv_hs(data,7);
    _rx_recv_hs(data,6);
    _rx_recv_hs(data,5);
    _rx_recv_hs(data,4);
    _rx_recv_hs(data,3);
    _rx_recv_hs(data,2);
    _rx_recv_hs(data,1);
    _rx_recv_hs(data,0);

    interrupts();

    if (rx_init_seen) { longjmp(rx_init_env, 3); }

    return data;
}

static uint16_t rx_recv8_hs (uint8_t handshake)
{
    uint8_t data = 0;

    rx_clr_out();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    noInterrupts();

    _rx_recv_hs(data,7);
    _rx_recv_hs(data,6);
    _rx_recv_hs(data,5);
    _rx_recv_hs(data,4);
    _rx_recv_hs(data,3);
    _rx_recv_hs(data,2);
    _rx_recv_hs(data,1);
    _rx_recv_hs(data,0);

    interrupts();

    if (rx_init_seen) { longjmp(rx_init_env, 4); }

    return uint16_t(data);
}

static uint16_t rx_recv (uint8_t n)
{
    if (n == 12)      return rx_recv12_hs(FALSE); else
    if (n ==  8)      return  rx_recv8_hs(FALSE); else
    if (rx_tst_12b()) return rx_recv12_hs(FALSE); else
                      return  rx_recv8_hs(FALSE);
}

static uint16_t rx_recv (void)
{
    if (rx_tst_12b()) return rx_recv12_hs(FALSE); else
                      return  rx_recv8_hs(FALSE);
}

static uint16_t rx_recv_hs (uint8_t n)
{
    if (n == 12)      return rx_recv12_hs(TRUE); else
    if (n ==  8)      return  rx_recv8_hs(TRUE); else
    if (rx_tst_12b()) return rx_recv12_hs(TRUE); else
                      return  rx_recv8_hs(TRUE);
}

static uint16_t rx_recv_hs (void)
{
    if (rx_tst_12b()) return rx_recv12_hs(TRUE); else
                      return  rx_recv8_hs(TRUE);
}



//
// transmit a 12/8 bit word to the RX interface, msb first
//

#define _rx_xmit_hs(xxx,yyy) { if ((xxx)&(1<<(yyy))) { delay_1c(); rx_set_datao(); } else { rx_clr_datao(); delay_2c(); } delay_1c(); rx_set_shift(); delay_1c(); rx_clr_shift(); }

static void rx_xmit12_hs (uint16_t value, uint8_t handshake)
{
    uint16_t data = value;

    noInterrupts();

    if (handshake) { rx_clr_request(); }

    rx_clr_shift();
    rx_set_out();
    delay_3c();

    _rx_xmit_hs(data,11);
    _rx_xmit_hs(data,10);
    _rx_xmit_hs(data,9);
    _rx_xmit_hs(data,8);
    _rx_xmit_hs(data,7);
    _rx_xmit_hs(data,6);
    _rx_xmit_hs(data,5);
    _rx_xmit_hs(data,4);
    _rx_xmit_hs(data,3);
    _rx_xmit_hs(data,2);
    _rx_xmit_hs(data,1);
    _rx_xmit_hs(data,0);

    delay_3c();
    rx_clr_datao();

    interrupts();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    rx_clr_out();

    if (rx_init_seen) { longjmp(rx_init_env, 5); }

    return;
}

static void rx_xmit8_hs (uint16_t value, uint8_t handshake)
{
    uint8_t data = value;

    noInterrupts();

    if (handshake) { rx_clr_request(); }

    rx_clr_shift();
    rx_set_out();
    delay_3c();

    _rx_xmit_hs(data,7);
    _rx_xmit_hs(data,6);
    _rx_xmit_hs(data,5);
    _rx_xmit_hs(data,4);
    _rx_xmit_hs(data,3);
    _rx_xmit_hs(data,2);
    _rx_xmit_hs(data,1);
    _rx_xmit_hs(data,0);

    delay_3c();
    rx_clr_datao();

    interrupts();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    rx_clr_out();

    if (rx_init_seen) { longjmp(rx_init_env, 6); }

    return;
}

static void rx_xmit (uint16_t data, uint8_t n)
{
    if (n == 12)      rx_xmit12_hs(data,FALSE); else
    if (n ==  8)       rx_xmit8_hs(data,FALSE); else
    if (rx_tst_12b()) rx_xmit12_hs(data,FALSE); else
                       rx_xmit8_hs(data,FALSE);
    return;
}

static void rx_xmit (uint16_t data)
{
    if (rx_tst_12b()) rx_xmit12_hs(data,FALSE); else
                       rx_xmit8_hs(data,FALSE);
    return;
}

static void rx_xmit_hs (uint16_t data, uint8_t n)
{
    if (n == 12)      rx_xmit12_hs(data,TRUE); else
    if (n ==  8)       rx_xmit8_hs(data,TRUE); else
    if (rx_tst_12b()) rx_xmit12_hs(data,TRUE); else
                       rx_xmit8_hs(data,TRUE);
    return;
}

static void rx_xmit_hs (uint16_t data)
{
    if (rx_tst_12b()) rx_xmit12_hs(data,TRUE); else
                       rx_xmit8_hs(data,TRUE);
    return;
}



//
// transmit an extended status word
//
static void rx_xmit_es (uint16_t data)
{
    if (debug) debug->printf("RX: rx_xmit_es(%04o)\n", data);

    if (rx_tst_dma() || rx_tst_12b() && rx.type == RX_TYPE_RX02) {
        // RX211/RXV21 or RX28 attached
        rx_set_done();
        rx_clr_request();
        rx_xmit(data, 12);
        rx_set_request();
    } else if (rx_tst_12b()) {
        // RX8E attached
        rx_xmit(data, 12);
     } else {
        // RX11/RXV11 attached
        rx_xmit(data, 8);
    }

    return;
}



//
// setup error/status word
//
static uint16_t rx_init_es (void)
{
    uint16_t es;

    // clear all bits except init done
    es = (rx.es & RXES_ID);
    // insert drive type (RX28 only w/ RX02)
    if (rx.type == RX_TYPE_RX02 && rx_tst_12b()) es |= RXES_RX02;
    // insert unit selected
    if (rx.unit == 1 && rx_tst_dma()) es |= RXES_UNITSEL;
    // insert drive ready for that unit
    if (rx.drv[rx.unit].rdy) es |= RXES_DRVRDY;
    // insert density for that unit
    if (rx.drv[rx.unit].den == RX_DEN_DD) es |= RXES_DRVDEN;

    return es;
}

// setup error/status with flags:  RXES_CRC, RXES_DENERR, RXES_DELDATA, RXES_WCOVF are data flag options

static uint16_t rx_init_es (uint16_t data)
{
    return rx_init_es() | data;
}



// PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC



//
// initialize the RX subsystem
//
//    flag:  TRUE for powerup init; FALSE for INIT pulse seen
//
void rx_initialize (uint8_t flag)
{
    uint8_t i;

    // led status
    led_state(red, on);
    led_state(green, off);
    led_state(yellow, off);

    // setup at power up ... required first time only
    if (flag) {

        // setup pins
        pinMode(PIN_CTLR_DMA_MODE_L, INPUT);
        pinMode(PIN_CTLR_DATAI_H, INPUT);
        pinMode(PIN_CTLR_12BIT_H, INPUT);
        pinMode(PIN_CTLR_INIT_H, INPUT); // interrupt
        pinMode(PIN_CTLR_RUN_H, INPUT);
        //
        pinMode(PIN_CTLR_TR_RQST_H, OUTPUT);
        pinMode(PIN_CTLR_DATAO_H, OUTPUT);
        pinMode(PIN_CTLR_SHIFT_H, OUTPUT);
        pinMode(PIN_CTLR_ERROR_H, OUTPUT);
        pinMode(PIN_CTLR_ACLO_H, OUTPUT);
        pinMode(PIN_CTLR_DONE_H, OUTPUT);
        pinMode(PIN_CTLR_OUT_H, OUTPUT);

        // setup RX INIT interrupt
        rx_init_seen = 0;
        attachInterrupt(digitalPinToInterrupt(PIN_CTLR_INIT_H), rx_intr_init, RISING);

    }

    // setup control outputs
    rx_clr_out();
    rx_clr_aclo();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();
    rx_clr_request();

    // toggle ACLO at initial reset
    if (flag) { rx_set_aclo(); delay(100); rx_clr_aclo(); }

    // wait for INIT to go away
    uint32_t start = millis(); // current time, in ms
    uint32_t expire = 10000UL; // 10 sec delay
    if (debug) debug->printf("RX: waiting for INIT to clear ... t=%lums\n", millis());
    while ((rx_init_seen || rx_tst_init()) && millis()-start <= expire) { rx_init_seen = 0; } 
    if (debug) debug->printf("RX: INIT has %scleared t=%lums\n", rx_tst_init() ? "NOT " : "", millis());

    // setup drive data structures
    rx.type = rx_type;
    rx.fcn.code = 8;
    rx.fcn.name = "INIT";
    rx.den = rx.type == RX_TYPE_RX02 ? RX_DEN_DD : RX_DEN_SD;
    rx.cs = 0;
    rx.es = 0;
    rx.ta = 0;
    rx.sa = 0;
    rx.wc = 0;
    rx.bc = 0;
    rx.len = 0;
    rx.pos = 0;
    rx.unit = 0;
    rx.ecode = 0;

    // setup drive state
    for (i = 0; i < RX_NUNITS; ++i) {
        if (flag) sprintf(rx.drv[i].name, "RX%d.DSK", i);
        rx_set_unit_file(i, NULL);
    }

    // clear sector buffer
    memset(rx.buffer, 0x00, sizeof(rx.buffer));

    // indicate SD card access in progress
    led_state(yellow, on);

    // copy drive 0 boot sector into the buffer
    rx.drv[0].ta = rx.ta = 1;
    rx.drv[0].sa = rx.sa = 1;
    rx.drv[0].len = rx.len = rx_sec_size(rx.drv[0].den);
    rx.drv[0].pos = rx.pos = (rx.ta*RX_NSECS + (rx.sa - 1)) * rx.len;
    sd_read_bytes(rx.drv[0].name, rx.pos, rx.buffer, rx.len);

    // indicate SD card access complete
    led_state(yellow, off);

    // init time delay
    rx_timing(RX_INIT_TIME);

    // setup error/status bits
    rx.es = rx_init_es(RXES_ID);

    // transmit status on INIT
    rx_xmit_es(rx.es);

    // led status
    led_state(red, off);

    return;
}



//
// setup the file for rx drive unit
//
void rx_set_unit_file (uint8_t unit, char *name)
{
    uint32_t size;

    // setup file name if provided
    if (name) strcpy(rx.drv[unit].name, name);

    // get size of (existing) file, check it for SD or DD or none
    size = sd_get_file_size(rx.drv[unit].name);
    if (!(rx.type == RX_TYPE_RX01 && size == rx_dsk_size(RX_DEN_SD) ||
          rx.type == RX_TYPE_RX02 && (size == rx_dsk_size(RX_DEN_SD) || size == rx_dsk_size(RX_DEN_DD)))
        ) sd_set_file_size(rx.drv[unit].name, rx.type == RX_TYPE_RX01 ? rx_dsk_size(RX_DEN_SD) : rx_dsk_size(RX_DEN_DD));

    // set initial state from common block
    rx.drv[unit].ta = rx.ta;
    rx.drv[unit].sa = rx.sa;
    rx.drv[unit].len = rx.len;
    rx.drv[unit].pos = rx.pos;

    // set drive ready if file exists and is right size (SD or DD)
    rx.drv[unit].rdy = sd_get_file_size(rx.drv[unit].name) > 0;

    // set drive density based on file size
    rx.drv[unit].den = sd_get_file_size(rx.drv[unit].name) == rx_dsk_size(RX_DEN_DD) ? RX_DEN_DD : RX_DEN_SD;

    // zap the deleted data state to normal
    memset(rx.drv[unit].dd, RX_NORMAL_DATA, sizeof(rx.drv[unit].dd));

    return;
}



//
// setup the RX02 subsystem debug port
//
void rx_debug (HardwareSerial *serialPort)
{
    // some status
    if (debug != NULL && serialPort == NULL) debug->printf("RX: debug disabled\n");
    if (debug == NULL && serialPort != NULL) serialPort->printf("RX: debug enabled\n");

    // set debug serial port (or not)
    debug = serialPort;

    // and done
    return;
}



//
// print RX emulator state
//
void rx_print_state (HardwareSerial *prt)
{
    uint8_t i;
    char den[] = { 'S', 'D', 'Q' };

    // dump to user supplied port if not null
    if (prt == NULL) prt = debug;
    // else try debug port, else return
    if (prt == NULL) return;

    prt->printf("\n--- RX Emulator State Dump ---\n\n");
    prt->printf("    i/f width = %d.\n",   rx_get_bits());
    prt->printf("     i/f mode = %s\n",    rx_tst_dma() ? "DMA" : "PIO");
    prt->printf("     i/f init = %o\n",    rx_tst_init());
    prt->printf("      i/f run = %o\n",    rx_tst_run());
    prt->printf("\n");
    prt->printf("        rx.cs = %06o\n",  rx.cs);
    prt->printf("        rx.es = %04o\n",  rx.es);
    prt->printf("        rx.wc = %03o\n",  rx.wc);
    prt->printf("        rx.bc = %03o\n",  rx.bc);
    prt->printf("        rx.ta = %03o\n",  rx.ta);
    prt->printf("        rx.sa = %03o\n",  rx.sa);
    prt->printf("       rx.pos = %lu.\n",  rx.pos);
    prt->printf("       rx.len = %u.\n",   rx.len);
    prt->printf("       rx.den = %cD\n",   den[rx.den]);
    prt->printf("      rx.unit = %o\n",    rx.unit);
    prt->printf("      rx.type = RX0%d\n", rx.type+1);
    prt->printf("     rx.ecode = %04o\n",  rx.ecode);
    prt->printf("  rx.fcn.code = %o\n",    rx.fcn.code);
    prt->printf("  rx.fcn.name = %s\n",    rx.fcn.name);
    for (i = 0; i < RX_NUNITS; ++i) {
        prt->printf("\n");
        prt->printf("    rx.drv[%d].name = '%s'\n", i,  rx.drv[i].name);
        prt->printf("     rx.drv[%d].rdy = %c\n", i, rx.drv[i].rdy ? 'Y' : 'N');
        prt->printf("     rx.drv[%d].den = %cD\n", i, den[rx.drv[i].den]);
        prt->printf("      rx.drv[%d].ta = %03o\n", i, rx.drv[i].ta);
        prt->printf("      rx.drv[%d].sa = %03o\n", i, rx.drv[i].sa);
        prt->printf("     rx.drv[%d].pos = %lu.\n", i, rx.drv[i].pos);
        prt->printf("     rx.drv[%d].len = %u.\n", i, rx.drv[i].len);
    }

    return;
}



//
// execute an RX function
//
void rx_function (void)
{
    uint16_t i, j;
    uint16_t value;

    // setup for a new request
    rx_clr_request();
    rx_set_done();

    // setup INIT seen callback
    if (setjmp(rx_init_env)) {
        // return to here on longjmp(rx_init_env,N);
        rx_init_seen = 0;
        rx_initialize(false);
        return;
    }

    // wait for RUN or INIT
    uint8_t stat = rx_wait_run_or_init(250);
    // return if timeout with no RUN or INIT
    if (stat == RX_SAW_NONE) return;
    // fall thru if RUN seen ...

    // RUN seen, process command

    // receive a command word: 12b on RX211 or RX8E/28 in 12b mode; 8b otherwise
    rx.cs = rx_recv(rx_tst_dma() || rx_tst_12b() ? 12 : 8);
    if (debug) debug->printf("RX: cmd=%04o\n", rx.cs);

    // led status
    led_state(red, off);
    led_state(green, on);
    led_state(yellow, off);

    // separate out the function field
    rx.fcn.code = (rx.cs & RXCS_FUNCTION)>>1;
    rx.fcn.name = fcn_name_list[rx.fcn.code];

    // separate out the unit number in the command
    rx.unit = (rx.cs & RXCS_UNITSEL) ? 1 : 0;

    // separate out the density flag in the command
    rx.den = (rx.cs & RXCS_DENSEL) ? RX_DEN_DD : RX_DEN_SD;

    // setup error/status bits
    rx.es = rx_init_es();

    // initial error code: success!
    rx.ecode = RXERR_SUCCESS;

    // print command
    if (debug) debug->printf("RX: %s unit=%o den=%o\n", rx.fcn.name, rx.unit, rx.den);

    // decode command function
    switch (rx.fcn.code) {

    // === buffer fill or empty ===
    //
    // DMA: command(12), wordcount(8), N*databytes(8)
    // PIO: command(12), N*databytes(8 or 12)
    //
    case RXFCN_FILL:
    case RXFCN_EMPTY:

        // compute word count
        if (rx_tst_dma()) {
            // if DMA mode, receive word count from interface
            rx.wc = rx_recv_hs(8);
            // receive bus address
            // rx.ba = rx_recv_hs(16); // done in RX211 hardware
        } else {
            // if PIO mode, compute word count from full sector size
            rx.wc = rx_sec_size(rx.den) >> (rx_tst_8b() ? 1 : 2);
        }
        if (debug) debug->printf("RX: %s wc=%03o\n", rx.fcn.name, rx.wc);

        // transform word count to byte count for transfer
        rx.bc = 2*rx.wc;

        // check for word count overflow
        if (rx.bc > rx_sec_size(rx.drv[rx.unit].den)) {
            if (debug) debug->printf("RX: %s wc=%03o WCOVF\n", rx.fcn.name, rx.wc);
            rx.ecode = RXERR_WCOVF;
            rx.es |= RXES_WCOVF;
            goto error;
        }

        // transfer data words in/out
        if (rx.fcn.code == RXFCN_EMPTY) {
            // empty buffer (RX controller buffer to host interface)
            for (i = 0; i < rx.bc; ++i) {
                if (rx_tst_8b()) {
                    // extract 8b values from buffer to send as 8b
                    value = rx.buffer[i];
                    rx_xmit_hs(value, 8);
                    if (debug) debug->printf("RX: %s d[%03o]=%03o\n", rx.fcn.name, i, value);
                } else {
                    // 12b values are packed into the first 2/3 of the sector; read 2 8b entries and build 12b value
                    j = 3*i/2;
                    if (i & 1) {
                        // odd byte
                        value = ((rx.buffer[j+0] & 017) << 8) | rx.buffer[j+1];
                    } else {
                        // even byte
                        value = (rx.buffer[j+0] << 4) | ((rx.buffer[j+1] >> 4) & 017);
                    }
                    rx_xmit_hs(value, 12);
                    if (debug) debug->printf("RX: %s d[%03o]=%04o\n", rx.fcn.name, i, value);
                }
             }
        } else {
            // fill buffer (host interface to RX controller buffer)
            for (i = 0; i < rx.bc; ++i) {
                if (rx_tst_8b()) {
                    // 8b values get stuffed into 8b buffer directly
                    value = rx_recv_hs(8);
                    rx.buffer[i] = value;
                    if (debug) debug->printf("RX: %s d[%03o]=%03o\n", rx.fcn.name, i, value);
                } else {
                    // 12b values get split into 4b/8b and merged into two 8b entries
                    value = rx_recv_hs(12);
                    j = 3*i/2;
                    if (i & 1) {
                        // odd byte
                        rx.buffer[j+0] |= (value >> 8) & 017;
                        rx.buffer[j+1] = value;
                    } else {
                        // even byte
                        rx.buffer[j+0] = value >> 4;
                        rx.buffer[j+1] = (value & 017) << 4;
                    }
                    if (debug) debug->printf("RX: %s d[%03o]=%04o\n", rx.fcn.name, i, value);
                }
            }
            // zero fill any unwritten bytes in the buffer
            for (i = rx.bc, j = rx_sec_size(rx.den); i < j; ++i) rx.buffer[i] = 0;
        }

        // indicate transfer is complete
        rx.wc = 0;

        break;

    // === read/write sector ===
    //
    // command(12), sector(8 or 12), track(8 or 12)
    //
    case RXFCN_RDSECT:
    case RXFCN_WRSECT:  
    case RXFCN_WRDDSECT:

        // first word is sector address
        rx.sa = rx_recv_hs() & 037;
        if (debug) debug->printf("RX: %s sa=%d.\n", rx.fcn.name, rx.sa);

        // second word is track address
        rx.ta = rx_recv_hs() & 0177;
        if (debug) debug->printf("RX: %s ta=%d.\n", rx.fcn.name, rx.ta);

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, clear drive ready bit and error
            rx.es &= ~RXES_DRVRDY;
            goto error;
        }

        // check track access is valid
        if (rx.ta >= RX_NTRKS) {
            rx.ecode = RXERR_TRKERR;
            goto error;
        }

        // check density matches, error if does not
        if (rx.type != RX_TYPE_RX01 && rx.den != rx.drv[rx.unit].den) {
            rx.es |= RXES_DENERR;
            rx.ecode = RXERR_DENERR;
            goto error;
        }

        // check sector address is valid
        if (rx.sa < 1 || rx.sa > RX_NSECS) {
            rx.ecode = RXERR_SECFAIL;
            goto error;
        }

        // compute length of transfer (sector size)
        rx.len = rx_sec_size(rx.drv[rx.unit].den);
        // compute byte offset into disk image
        rx.pos = (rx.ta*RX_NSECS + (rx.sa-1)) * rx.len;
        // print details if in debug mode
        if (debug) debug->printf("RX: %s pos=%lu. len=%u.\n", rx.fcn.name, rx.pos, rx.len);

        // bail on INIT seen
        if (rx_init_seen) { longjmp(rx_init_env, 20); }

        // indicate SD card access in progress
        led_state(yellow, on);

        // do the read or write
        if (rx.fcn.code == RXFCN_RDSECT) {
            // do a read; copy data into buffer from diskimage@offset
            value = sd_read_bytes(rx.drv[rx.unit].name, rx.pos, rx.buffer, rx.len);
        } else {
            // do a write
            if (rx.fcn.code == RXFCN_WRDDSECT) {
                bitWrite(rx.drv[rx.unit].dd[rx.ta], rx.sa, RX_DELETED_DATA);
            } else {
                bitWrite(rx.drv[rx.unit].dd[rx.ta], rx.sa, RX_NORMAL_DATA);
            }
            // copy data from rx.buffer[] to diskimage@offset
            value = sd_write_bytes(rx.drv[rx.unit].name, rx.pos, rx.buffer, rx.len);
        }
        if (bitRead(rx.drv[rx.unit].dd[rx.ta], rx.sa) == RX_DELETED_DATA) rx.es |= RXES_DELDATA;

        // SD card access complete
        led_state(yellow, off);

        // simulate timing
        rx_timing(rx_seek_time(abs(rx.ta - rx.drv[rx.unit].ta)));

        // update state info for drive
        rx.drv[rx.unit].ta = rx.ta;
        rx.drv[rx.unit].sa = rx.sa;
        rx.drv[rx.unit].pos = rx.pos;
        rx.drv[rx.unit].len = rx.len;

        // check for read/write error
        if (value != rx.len) {
            rx.ecode = RXERR_IDMFAIL;
            goto error;
        }

        break;

    // === set media density ===
    //
    // command(12), key(8 or 12)
    //
    case RXFCN_SETMEDIA:

        // this function is a NOP for the RX01
        if (rx.type == RX_TYPE_RX01) break;

        // first word must be the magic key 'I'
        value = rx_recv_hs();
        // check key for expected value
        if (value != 'I') {
            rx.ecode = RXERR_KEYERR;
            goto error;
        }

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, clear drive ready bit and error
            rx.es &= ~RXES_DRVRDY;
            goto error;
        }

        // simulate timing
        rx_timing(RX_SETMEDIA_TIME);

        // bail on INIT seen
        if (rx_init_seen) { longjmp(rx_init_env, 21); }

        // indicate SD card access in progress
        led_state(yellow, on);

        // change density to requested value
        sd_set_file_size(rx.drv[rx.unit].name, rx_dsk_size(rx.den));
        rx.drv[rx.unit].den = rx.den;

        // indicate SD card access complete
        led_state(yellow, off);

        break;

    // === read status ===
    //
    // command(12)
    //
    case RXFCN_RDSTAT:

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, clear drive ready bit and error
            rx.es &= ~RXES_DRVRDY;
            goto error;
        }

        // simulate timing
        rx_timing(RX_RDSTAT_TIME);

        // check that density matches, error if does not
        if (rx.type != RX_TYPE_RX01 && rx.den != rx.drv[rx.unit].den) {
            rx.es |= RXES_DENERR;
            rx.ecode = RXERR_DENERR;
            goto error;
        }

        break;

    // === read error code ===
    //
    // command(12)
    //
    case RXFCN_RDERROR:

        // simulate timing
        rx_timing(RX_RDERROR_TIME);

        // only executes on RX211/RXV21; return four 16b status words
        if (rx_tst_dma()) {

            // receive bus address
            // rx.ba = rx_recv_hs(16); // done in RX211 hardware

            // generate status byte
            value = (rx.unit == 1               ? (1<<7) : 0)
                  | (rx.drv[1].den == RX_DEN_DD ? (1<<6) : 0)
                  | (rx.drv[rx.unit].rdy        ? (1<<5) : 0)
                  | (rx.drv[0].den == RX_DEN_DD ? (1<<4) : 0)
                  | (rx.den == RX_DEN_DD        ? (1<<0) : 0);

            // word 1
            rx_xmit_hs(rx.ecode, 8);
            rx_xmit_hs(rx.wc, 8);

            // word 2
            rx_xmit_hs(rx.drv[0].ta, 8);
            rx_xmit_hs(rx.drv[1].ta, 8);

            // word 3
            rx_xmit_hs(rx.ta, 8);
            rx_xmit_hs(rx.sa, 8);

            // word 4
            rx_xmit_hs(value, 8);
            rx_xmit_hs(rx.drv[rx.unit].ta, 8);

        }

        break;

    } // switch (rx.fcn.num)

done:
    rx_xmit_es(rx.es);
    led_state(green, off);
    return;

error:
    rx_set_error();
    led_state(red, on);
    goto done;
}



// the end
 
An update on my implementation. I have now completed and tested the ascii command based user interface, so I can now reassign files to drives, etc.

I have done a lot of testing with the DEC RX02/RX211 performance test (AH-E513C-MC__RX11,RX01,RX02__RX02_SS_PERF_EXER__CZRXDC0) and it runs flawlessly. :)

However, doing an INIT DY0: from XXDP fails (freezes), and booting from a known-good RX02 XXDP bootable image (boots on SIMH) using 'b dy0' on my 11/44 halts. :-(

Unfortunately XXDP DY.SYS driver source code is not available AFAIK, and disassembling it so far has not been fruitful to understand the details of the driver.

Also the DEC RX02/RX211 logic test (AH-E626B-MC__RX11,RX01,RX02__RX02_FCTN-LGC__CZRXFB0) fails miserably from the get go (not really unexpected): :-(

Code:
.R ZRXF??
ZRXFB0.BIC

DRSSM-G2
CZRXFB0-0-0
RX02 FUNCTION-LOGIC TEST
UNIT IS RX02
RSTRT ADR 145702
DR>STA

CHANGE HW (L)  ? Y

# UNITS (D)  ? 2

UNIT 0
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ?
EXP WRD-CR (O)  0 ?
BR-LEVEL   (O)  5 ?

UNIT 1
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ? 1
EXP WRD-CR (O)  0 ?
BR-LEVEL   (O)  5 ?

CHANGE SW (L)  ? N

 IS FLOPPY SYSTEM CONTAINING UNIT #00
  POWERED DOWN (L) N ? N

CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 017 SUB 000 PC: 003476
 CONTROLLER-INTERFACE - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            CONTROLLER - M7744
            INTERFACE - M8256

  UNIT#0 RXCSR=004044 RXESR=000244 CMD=000017 ->READ ERROR CODE
  ERR CODE=000 ->
  WORD CNT=000
  CUR TRK DV0= 1. CUR TRK DV1= 0.
  TARGET TRK = 1. TARGET SEC = 1. SOFT STAT=160 BAD TRK= 1.

CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 017 SUB 000 PC: 003476
 CONTROLLER-INTERFACE - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            CONTROLLER - M7744
            INTERFACE - M8256

  UNIT#0 RXCSR=004044 RXESR=000244 CMD=000001 ->FILL BUFFER
CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 017 SUB 000 PC: 003476
 CONTROLLER-INTERFACE - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            CONTROLLER - M7744
            INTERFACE - M8256

  UNIT#0 RXCSR=004044 RXESR=000244 CMD=000003 ->EMPTY BUFFER
CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 017 SUB 000 PC: 003476
 CONTROLLER-INTERFACE - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            INTERFACE - M8256
            CONTROLLER - M7744
            INTERFACE - M8256

  UNIT#0 RXCSR=134044 RXESR=130264 CMD=000013 ->READ MAINT. STATUS
CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 018 SUB 000 PC: 003476
 NPR - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            INTERFACE - M8256
            CONTROLLER - M7744
            INTERFACE->CONTROLLER CABLE

  UNIT#0 RXCSR=004444 RXESR=000244 CMD=000401 ->FILL BUFFER
CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 018 SUB 000 PC: 003476
 NPR - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            INTERFACE - M8256
            CONTROLLER - M7744
            INTERFACE->CONTROLLER CABLE

  UNIT#0 RXCSR=004444 RXESR=000244 CMD=000403 ->EMPTY BUFFER
CZRXFB0 SYS FTL ERR  00050 ON UNIT 00 TST 025 SUB 000 PC: 003476
 CONTROLLER-READ*WRITE ELECT - LGC TST
  NO PROG "INIT DONE" ERROR
  POSSIBLE FAILING "FRU'S":
            INTERFACE - M8256
            CONTROLLER - M7744
            INTERFACE - M8256

  UNIT#0 RXCSR=004044 RXESR=000244 CMD=000017 ->READ ERROR CODE
  ERR CODE=000 ->
  WORD CNT=000
  CUR TRK DV0= 1. CUR TRK DV1= 0.
  TARGET TRK = 1. TARGET SEC = 1. SOFT STAT=160 BAD TRK= 1.

I'm still trying to track down how the " NO PROG "INIT DONE" ERROR" is determined and what it means. What bits miscompare to cause the error is still a mystery.

Luckily a PDF of the same binary I'm using is available at: www.retrocmp.com/tools/pdp-11-diagnostic-database however, this diagnostic was not written in a very user friendly way to debug hardware failures but really is intended for module level replacement as a GO/NOGO test (as was DEC's modus operandi in the later years).

My own custom written PDP-11 diagnostics that program the RX211 registers and do all the standard sets of accesses (buffer empty/fill, sector read/write, etc) all pass error free (both on my RX02 emulator hardware, and under SIMH).

Obviously something in the RX02 emulator implementation is not yet quite right, so more debugging is in the plan.

Don
 
Last edited:
Don,

I am thinking that when I get some free time, I will want to study your code enough to patch it to work on Bela's hardware. Otherwise I may just put the Due and his hardware interface to the side and move forward with a Mega and your hardware. Unfortunately my day job and chores have been killing my supply of free time! Please keep posting your progress though!

Lou
 
Don,

I am thinking that when I get some free time, I will want to study your code enough to patch it to work on Bela's hardware. Otherwise I may just put the Due and his hardware interface to the side and move forward with a Mega and your hardware. Unfortunately my day job and chores have been killing my supply of free time! Please keep posting your progress though!

Lou

I will keep posting new results as I get them. I have modified the above code significantly and will post a new version at some point, so the above code should be used as info only and not a hard reference.

The RX02/RX211 portion of the interface is still largely unchanged, however. Mostly changes in the outer level control structures. Still does not pass the base diags or XXDP boot, however.
 
So some significant progress. Finally was able to figure out why the XXDP DY.SYS driver was hanging, and the solution required in the emulator code to resolve it.

I can now boot XXDP from TU58EM DD0: and then do:

INIT DY0:
COPY DD0:*.* DY0:
COPY/BOOT DD0: DY0:
BOOT DY0:

and XXDP boots and runs from the RX02 emulator in double density mode. I can also then 'b dy0' from the 11/44 console and the RX02 emulator boots and runs XXDP error free.

I copied an RX02 disk image from my PC (that I generated and made a bootable XXDP image using the above commands in PDP11 SIMH) to the flash SD card on my Arduino RX02 emulator and did a 'b dy0' on that image from my 11/44 console. It booted and ran without error.

Don

For those interested, here is how the problem was found and resolved. Here is a trace I did running XXDP under SIMH and setting up R and W breakpoints (a recent and really useful feature, over and above a code execution breakpoint). The entire SIMH session was captured in a log file, and then post processed by a perl script.

Here is the relevant session where I have already typed the 'INIT DY1:' command to XXDP, and now it is asking me if I am sure and I say 'Y':

Code:
      1 *******  USER DATA ON DY1 WILL BE DESTROYED !
      1 *******  PROCEED?(Y/N/CR=N)Y
      1 RXDB (R)     MOV 2(R0),R1          (177172/000644)  (123456/000644)  (144426)
      1 RXCS (R)     BIT (SP),(R0)         (060440/100040)  (177170/004472)  (144056)         DD U1 RX02 DONE
      1 
      1 RXCS (W)     MOV (SP),(R0)         (060444/000421)  (177170/004620)  (144132)  FilBuf DD U1
      1 RXCS (R)     BIT (SP),(R0)         (060444/100200)  (177170/004620)  (144056)         DD U1 RX02 TR
      1 RXDB (W)     MOV 144236,2(R0)      (144236/000200)  (177172/000200)  (144212)    WC=0200
      1 RXCS (R)     BIT (SP),(R0)         (060444/100200)  (177170/004620)  (144056)         DD U1 RX02 TR
      1 RXDB (W)     MOV 143572,2(R0)      (143572/154266)  (177172/154266)  (144226)    BA=0154266
     43 RXCS (R)     BIT (SP),(R0)         (060442/100040)  (177170/004420)  (144056)         DD U1 RX02
      2 RXCS (R)     BIT (SP),(R0)         (060442/100040)  (177170/004460)  (144056)         DD U1 RX02 DONE
  
Real hardware hangs here; the above FILBUF command is received and executed correctly,
but after XXDP issues the WrSect command the RX02 emulator does not see it, and does not
respond. So the PDP-11 hangs in a loop.

      1 RXCS (W)     MOV (SP),(R0)         (060446/000425)  (177170/004624)  (144132)  WrSect DD U1
      1 RXCS (R)     BIT (SP),(R0)         (060444/100200)  (177170/004624)  (144056)         DD U1 RX02 TR
      1 RXDB (W)     MOV R3,2(R0)          (123456/000005)  (177172/000005)  (144150)    SA=05
      1 RXCS (R)     BIT (SP),(R0)         (060444/100200)  (177170/004624)  (144056)         DD U1 RX02 TR
      1 RXDB (W)     MOV R2,2(R0)          (123456/000001)  (177172/000001)  (144160)    TA=01
      2 RXCS (R)     BIT (SP),(R0)         (060446/100040)  (177170/004424)  (144056)         DD U1 RX02
      1 RXCS (R)     BIT (SP),(R0)         (060446/100040)  (177170/004464)  (144056)         DD U1 RX02 DONE
      1 RXCS (R)     BIT (SP),(R0)         (060440/100040)  (177170/004464)  (144056)         DD U1 RX02 DONE

Here is the disassembled portion of the DY.SYS driver it is hanging in:

Code:
144040:	WDONE:	MOV	#100040,-(SP)	; ERROR or DONE
144044:		BR	WAIT
144046:	WTR:	MOV	#100200,-(SP)	; ERROR or TR
144052:	WAIT:	TST	R1
144054:		BNE	15$
[B]144056:	10$:	BIT	(SP),(R0)	;************************ 11/44 processor is hanging in this tight loop
144060:		BEQ	10$		;************************ with (SP) = #100200 (ie, ERROR or TR expected)
[/B]144062:		BPL	15$
144064:		INC	R1
144066:	15$:	TST	(SP)+
144070:		TST	R1
144072:		RTS	PC

After looking at the microcode flows in the RX02 Technical Manual, I noticed that the sequence at 'OKDONE' where all successful commands flow thru to put the ES register into the DB register at command termination follows this sequence:

  • rx_clr_request
  • rx_set_out
  • rx_set_done
  • rx_xmit(data, 12)
  • rx_set_request

and then it jumps directly to the 'DECCMD' (decode command) routine which starts with this:

  • rx_clr_request
  • rx_clr_out
  • rx_set_done
  • rx_wait_run
  • rx_rcv_cmd

This is what the existing emulator code flow does (original Dickman RXM as well as Torok Arduino) and my code as well, which was based off original Dickman RXM.

BUT the key is that in the real microcode the actual time from the final rx_set_request in the OKDONE path to the rx_clr_request in the DECCMD routine is just two 200ns microcycles of the RX02 microcontroller (ie, a SET / JMP / CLR microsequence) or 400ns pulse width. In the PC or Arduino based controller the rx_set_request occurs at the end of the 'do_function' routine, and the rx_clr_request doesn't occur until the start of the next call to the 'do_function' routine. In my case when 'do_function' returned, I used the time between commands to process possible user commands before calling it again. So it was likely the effective width of the TR pulse high could be many microseconds (or even tens of microseconds) rather than just 400 nanoseconds. This was long enough to easily confuse the protocol between the RX211 interface and the RX02 emulator, and thus cause the PDP11 XXDP driver to get out of sync and looping.

The fix to have the Arduino code more closely emulate the RX02 microengine was to change the 'transmit RXES routine' called at the end of every command to this sequence:

Code:
        // RX211/RXV21 or RX28 attached
        rx_clr_request();
        rx_set_out();
        rx_set_done();
        rx_xmit(data, 12);
        rx_set_request();
        [B]rx_clr_request();[/B] [B]<=== ADD THIS LINE[/B]
        return;

This guarantees that the 'TR' pulse after the 12bit RXES status is transmitted is very short (and not up to hundreds of microseconds).

I still have not gotten the full RX02/RX211 diagnostic (ZRXFB0.BIC) to run successfully, but it is much happier now and spouting more reasonable error messages.

The RX02/RX211 performance test diagnostic (ZRXDC0.BIC) is still running fine without any errors, in all the various modes I have tested.
 
Last edited:
FYI here is an updated snapshot of the RX02 Emulator code:

Code:
//
// rx02_driver - Simple :-) RX02 driver
//
// (C) 2013 Don North <ak6dn_at_mindspring_dot_com>
//
// 21 Oct 2013 - donorth - Initial code
// 27 Mar 2016 - donorth - Ported to Arduino from CCS/Microchip
// 23 Sep 2016 - donorth - Updated rx_xmit_es() routine to fix XXDP hang.
//



//
// includes
//
#include "my_project.h"
#include "led_driver.h"
#include "rx02_driver.h"
#include "sdcard_driver.h"



//
// definitions
//

// options

#define USE_BIT_BUILTINS 0 // use bit builtins BitRead() etc vs use logical bit shift/mask operations

// RXCS control/status bit definitions

//efine RXCS_GO         (1<<0)      // GO bit                       (reference only; not used in drive)
#define RXCS_FUNCTION   (7<<1)      // function bitfield                            
#define RXCS_UNITSEL    (1<<4)      // unit select
//efine RXCS_DONE       (1<<5)      // function complete            (reference only; not used in drive)
//efine RXCS_INTENB     (1<<6)      // interrupt enable             (reference only; not used in drive)
//efine RXCS_8BIT       (1<<6)      // 8b mode                      (RX8E/RX28 ONLY)
//efine RXCS_TR         (1<<7)      // transfer request             (reference only; not used in drive)
//efine RXCS_MAINT      (1<<7)      // maintenance mode             (RX8E/RX28 ONLY)
#define RXCS_DENSEL     (1<<8)      // density select               (RX211/RX28 w/RX02 ONLY)
//efine RXCS_HEADSEL    (1<<9)      // head select                  (RX211/RX28 w/RX03 ONLY)
//efine RXCS_NU10      (1<<10)      // not used
//efine RXCS_RX02      (1<<11)      // RX02 interface               (reference only; not used in drive)
//efine RXCS_EXTADDR   (3<<12)      // upper bits of phys address   (reference only; not used in drive)
//efine RXCS_INIT      (1<<14)      // initialize RX                (reference only; not used in drive)
//efine RXCS_ERROR     (1<<15)      // error flag                   (reference only; not used in drive)

#define RXFCN_FILL        (0)       // fill buffer
#define RXFCN_EMPTY       (1)       // empty buffer
#define RXFCN_WRSECT      (2)       // write sector
#define RXFCN_RDSECT      (3)       // read sector
#define RXFCN_SETMEDIA    (4)       // set media density
#define RXFCN_RDSTAT      (5)       // read status
#define RXFCN_WRDDSECT    (6)       // write deleted data sector
#define RXFCN_RDERROR     (7)       // read error code

// RXES status bit definitions

//efine RXES_CRC        (1<<0)      // CRC aka READ error
//efine RXES_SIDRDY     (1<<1)      // side ready                   (RX211/RX28 ONLY)
//efine RXES_PERR       (1<<1)      // parity error                 (RX11/RX8E w/RX01 ONLY)
#define RXES_INIT       (1<<2)      // controller init done
#define RXES_RX02       (1<<3)      // set for RX02 drive           (RX28 w/ RX02 ONLY)
//efine RXES_ACLO       (1<<3)      // set for AC low               (RX211 w/RX02 ONLY)
//efine RXES_WPERR      (1<<3)      // set for write to WP drive    (RX11/RX8E w/RX01 ONLY)
#define RXES_DENERR     (1<<4)      // density error
#define RXES_DRVDEN     (1<<5)      // diskette double density
#define RXES_DELDATA    (1<<6)      // deleted data detected 
#define RXES_DRVRDY     (1<<7)      // selected drive ready
#define RXES_UNITSEL    (1<<8)      // unit selected                (RX211 w/RX02 ONLY)
//efine RXES_HEADSEL    (1<<9)      // head selected                (RX211 w/RX03 ONLY
#define RXES_WCOVF     (1<<10)      // word count overflow          (RX211 w/RX02 ONLY)
//efine RXES_NXM       (1<<11)      // nonexistent memory           (RX211 w/RX02 reference only; not used in drive)

// RX error codes

#define RXERR_SUCCESS    0000       // success, no error
//efine RXERR_DR0INIT    0010       // drive 0 failed to init
//efine RXERR_DR1INIT    0020       // drive 1 failed to init
//efine RXERR_STEPHOME   0030       // found home when stepping for init (RX01 ONLY)
#define RXERR_TRKERR     0040       // access to track > 76
#define RXERR_TRKFAIL    0050       // track not found
//efine RXERR_SELFDIAG   0060       // self diagnostic fail (RX01 ONLY)
#define RXERR_SECFAIL    0070       // sector not found
//efine RXERR_WRITEWP    0100       // write to a WP drive (RX01 ONLY)
//efine RXERR_SEPFAIL    0110       // no SEP clock in 40us
//efine RXERR_PREFAIL    0120       // preamble not found
#define RXERR_IDMFAIL    0130       // ID mark not found
//efine RXERR_CRCHEAD    0140       // CRC error on a header (RX01 ONLY)
//efine RXERR_TRKCMP     0150       // header track miscompare
//efine RXERR_IDMTRYS    0160       // too many tries for IDAM
//efine RXERR_DAMFAIL    0170       // data AM not found
//efine RXERR_CRCERR     0200       // CRC error on read
//efine RXERR_PERR       0210       // parity error on word from i/f to controller (RX01 ONLY)
//efine RXERR_RWFAIL     0220       // r/w failed maint test (RX02 ONLY)
#define RXERR_WCOVF      0230       // word count overflow (RX02 ONLY)
#define RXERR_DENERR     0240       // density error (RX02 ONLY)
#define RXERR_KEYERR     0250       // wrong key word for set density (RX02 ONLY)

// macros

// rx interface outputs set/clr

#if USE_BIT_BUILTINS
#define rx_set_done(xxx)     bitSet(PORTA,6)    /* digitalWrite(PIN_CTLR_DONE_H,HIGH) */
#define rx_clr_done(xxx)     bitClear(PORTA,6)  /* digitalWrite(PIN_CTLR_DONE_H,LOW) */
#define rx_set_error(xxx)    bitSet(PORTA,7)    /* digitalWrite(PIN_CTLR_ERROR_H,HIGH) */
#define rx_clr_error(xxx)    bitClear(PORTA,7)  /* digitalWrite(PIN_CTLR_ERROR_H,LOW) */
#define rx_set_aclo(xxx)     bitSet(PORTC,1)    /* digitalWrite(PIN_CTLR_ACLO_H,HIGH) */
#define rx_clr_aclo(xxx)     bitClear(PORTC,1)  /* digitalWrite(PIN_CTLR_ACLO_H,LOW) */
#define rx_set_shift(xxx)    bitSet(PORTC,2)    /* digitalWrite(PIN_CTLR_SHIFT_H,HIGH) */
#define rx_clr_shift(xxx)    bitClear(PORTC,2)  /* digitalWrite(PIN_CTLR_SHIFT_H,LOW) */
#define rx_set_out(xxx)      bitSet(PORTC,3)    /* digitalWrite(PIN_CTLR_OUT_H,HIGH) */
#define rx_clr_out(xxx)      bitClear(PORTC,3)  /* digitalWrite(PIN_CTLR_OUT_H,LOW) */
#define rx_set_datao(xxx)    bitSet(PORTC,5)    /* digitalWrite(PIN_CTLR_DATAO_H,HIGH) */
#define rx_clr_datao(xxx)    bitClear(PORTC,5)  /* digitalWrite(PIN_CTLR_DATAO_H,LOW) */
#define rx_set_request(xxx)  bitSet(PORTC,6)    /* digitalWrite(PIN_CTLR_TR_RQST_H,HIGH) */
#define rx_clr_request(xxx)  bitClear(PORTC,6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,LOW) */
// rx interface inputs test
#define rx_tst_pio(xxx)      bitRead(PINC,0)    /*  digitalRead(PIN_CTLR_DMA_MODE_L) */
#define rx_tst_dma(xxx)      (rx_tst_pio()?0:1) /* !digitalRead(PIN_CTLR_DMA_MODE_L) */
#define rx_tst_datai(xxx)    bitRead(PINC,4)    /*  digitalRead(PIN_CTLR_DATAI_H) */
#define rx_tst_12b(xxx)      bitRead(PINC,7)    /*  digitalRead(PIN_CTLR_12BIT_H) */
#define rx_tst_8b(xxx)       (rx_tst_12b()?0:1) /* !digitalRead(PIN_CTLR_12BIT_H) */
#define rx_tst_run(xxx)      bitRead(PINE,4)    /*  digitalRead(PIN_CTLR_RUN_H)) */
#define rx_tst_init(xxx)     bitRead(PINE,5)    /*  digitalRead(PIN_CTLR_INIT_H) */
#else
#define rx_set_done(xxx)     PORTA |=  (1<<6)  /* digitalWrite(PIN_CTLR_DONE_H,HIGH) */
#define rx_clr_done(xxx)     PORTA &= ~(1<<6)  /* digitalWrite(PIN_CTLR_DONE_H,LOW) */
#define rx_set_error(xxx)    PORTA |=  (1<<7)  /* digitalWrite(PIN_CTLR_ERROR_H,HIGH) */
#define rx_clr_error(xxx)    PORTA &= ~(1<<7)  /* digitalWrite(PIN_CTLR_ERROR_H,LOW) */
#define rx_set_aclo(xxx)     PORTC |=  (1<<1)  /* digitalWrite(PIN_CTLR_ACLO_H,HIGH) */
#define rx_clr_aclo(xxx)     PORTC &= ~(1<<1)  /* digitalWrite(PIN_CTLR_ACLO_H,LOW) */
#define rx_set_shift(xxx)    PORTC |=  (1<<2)  /* digitalWrite(PIN_CTLR_SHIFT_H,HIGH) */
#define rx_clr_shift(xxx)    PORTC &= ~(1<<2)  /* digitalWrite(PIN_CTLR_SHIFT_H,LOW) */
#define rx_set_out(xxx)      PORTC |=  (1<<3)  /* digitalWrite(PIN_CTLR_OUT_H,HIGH) */
#define rx_clr_out(xxx)      PORTC &= ~(1<<3)  /* digitalWrite(PIN_CTLR_OUT_H,LOW) */
#define rx_set_datao(xxx)    PORTC |=  (1<<5)  /* digitalWrite(PIN_CTLR_DATAO_H,HIGH) */
#define rx_clr_datao(xxx)    PORTC &= ~(1<<5)  /* digitalWrite(PIN_CTLR_DATAO_H,LOW) */
#define rx_set_request(xxx)  PORTC |=  (1<<6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,HIGH) */
#define rx_clr_request(xxx)  PORTC &= ~(1<<6)  /* digitalWrite(PIN_CTLR_TR_RQST_H,LOW) */
// rx interface inputs test
#define rx_tst_pio(xxx)     ((PINC & (1<<0)) ? 1 : 0) /* (digitalRead(PIN_CTLR_DMA_MODE_L)==HIGH) */
#define rx_tst_dma(xxx)     ((PINC & (1<<0)) ? 0 : 1) /* (digitalRead(PIN_CTLR_DMA_MODE_L)==LOW) */
#define rx_tst_datai(xxx)   ((PINC & (1<<4)) ? 1 : 0) /* digitalRead(PIN_CTLR_DATAI_H) */
#define rx_tst_12b(xxx)     ((PINC & (1<<7)) ? 1 : 0) /* (digitalRead(PIN_CTLR_12BIT_H)==HIGH) */
#define rx_tst_8b(xxx)      ((PINC & (1<<7)) ? 0 : 1) /* (digitalRead(PIN_CTLR_12BIT_H)==LOW) */
#define rx_tst_run(xxx)     ((PINE & (1<<4)) ? 1 : 0) /* (digitalRead(PIN_CTLR_RUN_H)==HIGH) */
#define rx_tst_init(xxx)    ((PINE & (1<<5)) ? 1 : 0) /* (digitalRead(PIN_CTLR_INIT_H)==HIGH) */
#endif

// INIT/RUN status

#define RX_SAW_NONE     0               // saw neither RUN or INIT
#define RX_SAW_RUN      1               // saw RUN and not INIT

// emulation type

#define RX_TYPE_NONE    0               // no type defined
#define RX_TYPE_RX01    1               // RX02 in RX01 mode
#define RX_TYPE_RX02    2               // RX02 in native mode
#define RX_TYPE_RX03    3               // RX02 in dual-sided RX03 mode (***UNSUPPORTED***)

// drive density

#define RX_DEN_SD       0                // single density mode (128B/sector)
#define RX_DEN_DD       1                // double density mode (256B/sector)
#define RX_DEN_QD       2               //  quad  density mode (512B/sector) (***UNSUPPORTED***)

// data type

#define RX_DATA_NORMAL  0               // normal data
#define RX_DATA_DELETED 1               // deleted data

// timing characteristics

#define RX_TIME_INIT      0             // initialize floppy drive
#define RX_TIME_SEEK      1             // read/write seek time, position dependent
#define RX_TIME_RDSTAT    2             // read status function
#define RX_TIME_RDERROR   3             // read error registers
#define RX_TIME_SETMEDIA  4             // set media density

// disk definitions

#define RX_NTRKS        77L             // number of tracks per disk
#define RX_NSECS        26L             // number of sectors per track
#define RX_NBPS         128L            // bytes per sector, single density
#define RX_NUNITS       2               // number if units

// convenience macros

#define rx_sec_size(den)    (RX_NBPS<<(den))             // bytes per sector at density
#define rx_trk_size(den)    (RX_NSECS*rx_sec_size(den))  // bytes per track at density
#define rx_dsk_size(den)    (RX_NTRKS*rx_trk_size(den))  // bytes per disk at density

#define rx_get_bits(xxx)    (rx_tst_12b() ? 12 : 8)      // number of bits signaled by interface

// buffer sizes

#define RX_BUFFER_SIZE      (rx_sec_size(RX_DEN_DD))     // maximum size sector buffer for double density



// PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE PRIVATE



volatile static uint8_t rx_run_seen; // set when RX RUN asserted (rising edge)

volatile static uint8_t rx_init_seen; // set when RX INIT asserted (rising edge)

static jmp_buf rx_init_env; // environment to invoke when INIT is asserted (ie, reset/error condition)

static char fcn_name_list[][8] = { "FILBUF", "EMPBUF", "WRSECT", "RDSECT", "SETMED", "RDSTAT", "WRDDSE", "RDERRC" };

static uint8_t rx_type = RX_TYPE_RX02; // default drive emulation, can be overwritten

struct drv_t {
    uint32_t dd[RX_NTRKS];  // normal or deleted data per track per sector (1 bit per sector 0..31)
    char     name[32];      // associated file name
    uint8_t  rdy;           // drive ready
    uint8_t  den;           // density
    uint8_t  ta;            // track address
    uint8_t  sa;            // sector address
    uint16_t len;           // read/write length
    uint32_t pos;           // read/write position
};

static struct rx_t {
    uint32_t cmdcnt;   // count of commands
    uint32_t errcnt;   // count of errors
    uint16_t cs;       // control/status image
    uint16_t es;       // error and status
    uint16_t tc;       // transfer count
    uint8_t  wc;       // word count
    uint8_t  ec;       // error code
    uint8_t  unit;     // active unit number
    uint8_t  den;      // density selected, DD or SD
    uint8_t  ta;       // track address
    uint8_t  sa;       // sector address
    uint8_t  type;     // emulation type: RX01, RX02
    uint8_t  timing;   // timing mode: 0=fastest, 2=normal/real
    uint16_t len;      // read/write length
    uint32_t pos;      // read/write position
    struct {
        uint8_t code;  // function selected (numeric 0..7)
        char *  name;  // function selected, ascii name id
    } fcn;
    struct drv_t drv[RX_NUNITS]; // drive specific parameters
    uint8_t buffer[RX_BUFFER_SIZE]; // sector buffer
} rx;

static HardwareSerial *debugPort = NULL; // debug serial output

static uint8_t debugLevel = 0; // debug level, 0=NONE, higher is more



//
// RX02 INIT assertion interrupt
//
static void rx_intr_init (void)
{
    rx_init_seen = 1;
    return;
}



//
// RX02 RUN assertion interrupt
//
static void rx_intr_run (void)
{
    rx_run_seen = 1;
    return;
}



//
// RX time delay routine
//
static void rx_timing (uint8_t type)
{
    uint16_t delta;
    uint16_t ms_delay;
    const uint16_t ms_amount = 5;

    // check basic timing mode
    if (rx.timing >= 1) {

        // otherwise decode timing parameter
        switch (type) {

            // initialize floppy drive
            case RX_TIME_INIT:
                ms_delay = 500;
                break;

            // read status function
            case RX_TIME_RDSTAT:
                ms_delay = 250;
                break;

            // read error registers
            case RX_TIME_RDERROR:
                ms_delay = 10;
                break;

            // set media density
            case RX_TIME_SETMEDIA:
                ms_delay = 10000;
                break;

            // read/write seek time, position dependent
            case RX_TIME_SEEK:
                // compute track move delta
                delta = abs(rx.ta - rx.drv[rx.unit].ta);
                // compute seek time based on delta track amount
                ms_delay = 20 + ((delta==0) ? 0 : (20 + delta*10));
                break;

            // none
            default:
                ms_delay = 0;
                break;
        }

        // for medium timing mode, make delays 1/10 of normal
        if (rx.timing == 1) ms_delay = ms_delay/10;

        // check for delay requested
        if (ms_delay > 0) {

            // delay in 5ms increments, checking for INIT
            while (ms_delay > ms_amount) {
                if (rx_init_seen) { longjmp(rx_init_env, 10); }
                delay(ms_amount);
                ms_delay -= ms_amount;
            }

            // final small delay less than 5ms
            if (rx_init_seen) { longjmp(rx_init_env, 10); }
            delay(ms_delay);

        }

    }

    // all done
    if (rx_init_seen) { longjmp(rx_init_env, 10); }
    return;
}



//
// wait until the RX_RUN or RX_INIT signal is asserted, or timeout occurs
//
static uint8_t rx_wait_run_or_init (uint32_t expire)
{
    uint32_t start = millis();

    do {
        if (rx_init_seen) { rx_init_seen = 0; longjmp(rx_init_env, 1); }
        if (rx_run_seen) { rx_run_seen = 0; return RX_SAW_RUN; }
    } while (millis()-start <= expire);

    // count timed out with neither RUN or INIT
    return RX_SAW_NONE;
}

// wait forever until the RX_RUN or RX_INIT signal is asserted

static uint8_t rx_wait_run_or_init (void)
{
    do {
        if (rx_init_seen) { rx_init_seen = 0; longjmp(rx_init_env, 2); }
        if (rx_run_seen) { rx_run_seen = 0; return RX_SAW_RUN; }
    } while (TRUE);
}



//
// receive a 12/8 bit word from the RX interface, msb first
//

#define _rx_recv_hs(xxx,yyy) { if (rx_tst_datai()) { delay_1c(); (xxx) |= (1<<(yyy)); } else { (xxx) &= ~(1<<(yyy)); delay_2c(); } delay_1c(); rx_set_shift(); delay_1c(); rx_clr_shift(); }

static uint16_t rx_recv12_hs (uint8_t handshake)
{
    uint16_t data = 0;

    rx_clr_out();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    noInterrupts();

    _rx_recv_hs(data,11);
    _rx_recv_hs(data,10);
    _rx_recv_hs(data,9);
    _rx_recv_hs(data,8);
    _rx_recv_hs(data,7);
    _rx_recv_hs(data,6);
    _rx_recv_hs(data,5);
    _rx_recv_hs(data,4);
    _rx_recv_hs(data,3);
    _rx_recv_hs(data,2);
    _rx_recv_hs(data,1);
    _rx_recv_hs(data,0);

    interrupts();

    if (rx_init_seen) { longjmp(rx_init_env, 3); }

    return data;
}

static uint16_t rx_recv8_hs (uint8_t handshake)
{
    uint8_t data = 0;

    rx_clr_out();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    noInterrupts();

    _rx_recv_hs(data,7);
    _rx_recv_hs(data,6);
    _rx_recv_hs(data,5);
    _rx_recv_hs(data,4);
    _rx_recv_hs(data,3);
    _rx_recv_hs(data,2);
    _rx_recv_hs(data,1);
    _rx_recv_hs(data,0);

    interrupts();

    if (rx_init_seen) { longjmp(rx_init_env, 4); }

    return uint16_t(data);
}

static uint16_t rx_recv (uint8_t n)
{
    if (n == 12)      return rx_recv12_hs(FALSE); else
    if (n ==  8)      return  rx_recv8_hs(FALSE); else
    if (rx_tst_12b()) return rx_recv12_hs(FALSE); else
                      return  rx_recv8_hs(FALSE);
}

static uint16_t rx_recv (void)
{
    if (rx_tst_12b()) return rx_recv12_hs(FALSE); else
                      return  rx_recv8_hs(FALSE);
}

static uint16_t rx_recv_hs (uint8_t n)
{
    if (n == 12)      return rx_recv12_hs(TRUE); else
    if (n ==  8)      return  rx_recv8_hs(TRUE); else
    if (rx_tst_12b()) return rx_recv12_hs(TRUE); else
                      return  rx_recv8_hs(TRUE);
}

static uint16_t rx_recv_hs (void)
{
    if (rx_tst_12b()) return rx_recv12_hs(TRUE); else
                      return  rx_recv8_hs(TRUE);
}



//
// transmit a 12/8 bit word to the RX interface, msb first
//

#define _rx_xmit_hs(xxx,yyy) { if ((xxx)&(1<<(yyy))) { delay_1c(); rx_set_datao(); } else { rx_clr_datao(); delay_2c(); } delay_1c(); rx_set_shift(); delay_1c(); rx_clr_shift(); }

static void rx_xmit12_hs (uint16_t value, uint8_t handshake)
{
    uint16_t data = value;

    noInterrupts();

    if (handshake) { rx_clr_request(); }

    rx_clr_shift();
    rx_set_out();
    delay_3c();

    _rx_xmit_hs(data,11);
    _rx_xmit_hs(data,10);
    _rx_xmit_hs(data,9);
    _rx_xmit_hs(data,8);
    _rx_xmit_hs(data,7);
    _rx_xmit_hs(data,6);
    _rx_xmit_hs(data,5);
    _rx_xmit_hs(data,4);
    _rx_xmit_hs(data,3);
    _rx_xmit_hs(data,2);
    _rx_xmit_hs(data,1);
    _rx_xmit_hs(data,0);

    delay_3c();
    rx_clr_datao();

    interrupts();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    rx_clr_out();

    if (rx_init_seen) { longjmp(rx_init_env, 5); }

    return;
}

static void rx_xmit8_hs (uint16_t value, uint8_t handshake)
{
    uint8_t data = value;

    noInterrupts();

    if (handshake) { rx_clr_request(); }

    rx_clr_shift();
    rx_set_out();
    delay_3c();

    _rx_xmit_hs(data,7);
    _rx_xmit_hs(data,6);
    _rx_xmit_hs(data,5);
    _rx_xmit_hs(data,4);
    _rx_xmit_hs(data,3);
    _rx_xmit_hs(data,2);
    _rx_xmit_hs(data,1);
    _rx_xmit_hs(data,0);

    delay_3c();
    rx_clr_datao();

    interrupts();

    if (handshake) { rx_set_request(); rx_wait_run_or_init(); rx_clr_request(); }

    rx_clr_out();

    if (rx_init_seen) { longjmp(rx_init_env, 6); }

    return;
}

static void rx_xmit (uint16_t data, uint8_t n)
{
    if (n == 12)      rx_xmit12_hs(data,FALSE); else
    if (n ==  8)       rx_xmit8_hs(data,FALSE); else
    if (rx_tst_12b()) rx_xmit12_hs(data,FALSE); else
                       rx_xmit8_hs(data,FALSE);
    return;
}

static void rx_xmit (uint16_t data)
{
    if (rx_tst_12b()) rx_xmit12_hs(data,FALSE); else
                       rx_xmit8_hs(data,FALSE);
    return;
}

static void rx_xmit_hs (uint16_t data, uint8_t n)
{
    if (n == 12)      rx_xmit12_hs(data,TRUE); else
    if (n ==  8)       rx_xmit8_hs(data,TRUE); else
    if (rx_tst_12b()) rx_xmit12_hs(data,TRUE); else
                       rx_xmit8_hs(data,TRUE);
    return;
}

static void rx_xmit_hs (uint16_t data)
{
    if (rx_tst_12b()) rx_xmit12_hs(data,TRUE); else
                       rx_xmit8_hs(data,TRUE);
    return;
}



//
// transmit an extended status word
//
static void rx_xmit_es (uint16_t data)
{
    if (debugLevel) debugPort->printf(F("RX: %s rx_xmit_es(%04o)\n\n"), rx.fcn.name, data);

    if (rx_tst_dma() || rx_tst_12b() && rx.type == RX_TYPE_RX02) {
        // RX211/RXV21 or RX28 attached
        rx_clr_request();
        rx_set_out();
        rx_set_done();
        rx_xmit(data, 12);
        rx_set_request();
        rx_clr_request();
    } else if (rx_tst_12b()) {
        // RX8E attached
        rx_xmit(data, 12);
     } else {
        // RX11/RXV11 attached
        rx_xmit(data, 8);
    }

    return;
}

static void rx_xmit_es (void)
{
    rx_xmit_es(rx.es);
    return;
}



//
// setup error/status word with flags:  RXES_CRC, RXES_DENERR, RXES_DELDATA, RXES_WCOVF are data flag options
//
static void rx_init_es (uint16_t data)
{
    // clear all bits
    rx.es = 0;

    // insert user data
    rx.es |= data;

    // insert drive ready for that unit
    if (rx.drv[rx.unit].rdy) rx.es |= RXES_DRVRDY;

    // certain bits for RX02/RX03
    if (rx.type == RX_TYPE_RX02 || rx.type == RX_TYPE_RX03) {

        // insert drive type (RX28 only)
        if (rx_tst_12b()) rx.es |= RXES_RX02;

        // insert unit selected (RX211 only)
        if (rx.unit == 1 && rx_tst_dma()) rx.es |= RXES_UNITSEL;

        // insert density for that unit
        if (rx.drv[rx.unit].den == RX_DEN_DD) rx.es |= RXES_DRVDEN;

    }

    // done
    return;
}

// setup error/status, no flags

static void rx_init_es (void)
{
    rx_init_es(0);
    return;
}



// PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC PUBLIC



//
// initialize the RX subsystem
//
//    flag:  TRUE for powerup init; FALSE for INIT pulse seen
//
void rx_initialize (uint8_t flag)
{
    uint8_t i;

    // led status
    led_state(red, on);
    led_state(green, off);
    led_state(yellow, off);

    // setup at power up ... required first time only
    if (flag) {

        // setup pins
        pinMode(PIN_CTLR_DMA_MODE_L, INPUT);
        pinMode(PIN_CTLR_DATAI_H, INPUT);
        pinMode(PIN_CTLR_12BIT_H, INPUT);
        pinMode(PIN_CTLR_INIT_H, INPUT); // interrupt capable
        pinMode(PIN_CTLR_RUN_H, INPUT); // interrupt capable
        //
        pinMode(PIN_CTLR_TR_RQST_H, OUTPUT);
        pinMode(PIN_CTLR_DATAO_H, OUTPUT);
        pinMode(PIN_CTLR_SHIFT_H, OUTPUT);
        pinMode(PIN_CTLR_ERROR_H, OUTPUT);
        pinMode(PIN_CTLR_ACLO_H, OUTPUT);
        pinMode(PIN_CTLR_DONE_H, OUTPUT);
        pinMode(PIN_CTLR_OUT_H, OUTPUT);

        // setup RX INIT interrupt
        rx_init_seen = 0;
        attachInterrupt(digitalPinToInterrupt(PIN_CTLR_INIT_H), rx_intr_init, RISING);

        // setup RX RUN interrupt
        rx_run_seen = 0;
        attachInterrupt(digitalPinToInterrupt(PIN_CTLR_RUN_H), rx_intr_run, RISING);

        // counters
        rx.cmdcnt = 0;
        rx.errcnt = 0;
    }

    // setup control outputs
    rx_clr_out();
    rx_clr_aclo();
    rx_clr_done();
    rx_clr_datao();
    rx_clr_error();
    rx_clr_shift();
    rx_clr_request();

    // toggle ACLO at initial reset
    if (flag) { rx_set_aclo(); delay(100); rx_clr_aclo(); }

    // wait for INIT to go away
    uint32_t start = millis(); // current time, in ms
    uint32_t expire = 10000UL; // 10 sec delay
    if (debugLevel) debugPort->printf(F("RX: waiting for INIT to clear ... t=%lums\n"), millis());
    while ((rx_init_seen || rx_tst_init()) && millis()-start <= expire) { rx_init_seen = 0; } 
    if (debugLevel) debugPort->printf(F("RX: INIT has %scleared t=%lums\n"), rx_tst_init() ? "NOT " : "", millis());

    // setup drive data structures
    rx.type = rx_type;
    rx.fcn.code = 8;
    rx.fcn.name = "INIT";
    rx.den = (rx.type == RX_TYPE_RX02 ? RX_DEN_DD : RX_DEN_SD);
    rx.cs = 0;
    rx.es = 0;
    rx.ec = 0;
    rx.ta = 0;
    rx.sa = 0;
    rx.wc = 0;
    rx.tc = 0;
    rx.len = 0;
    rx.pos = 0;
    rx.unit = 0;
    rx.timing = 0;

    // setup drive state
    for (i = 0; i < RX_NUNITS; ++i) {
        if (flag) sprintf(rx.drv[i].name, "RX%d.DSK", i);
        rx_unit_file(i);
    }

    // clear entire sector buffer
    memset(rx.buffer, 0x00, sizeof(rx.buffer));

    // indicate SD card access in progress
    led_state(yellow, on);

    // copy drive 0 boot sector into the buffer, if drive is ready
    rx.drv[0].ta  = rx.ta = 1;
    rx.drv[0].sa  = rx.sa = 1;
    rx.drv[0].len = rx.len = rx_sec_size(rx.drv[0].den);
    rx.drv[0].pos = rx.pos = (rx.ta*RX_NSECS + (rx.sa - 1)) * rx.len;
    if (rx.drv[0].rdy) sd_read_bytes(rx.drv[0].name, rx.pos, rx.buffer, rx.len);

    // indicate SD card access complete
    led_state(yellow, off);

    // init time delay
    rx_timing(RX_TIME_INIT);

    // setup error/status bits and transmit
    rx_init_es(RXES_INIT);
    rx_xmit_es();

    // led status
    led_state(red, off);

    return;
}



//
// setup the file for rx drive unit
//
char *rx_unit_file (uint8_t unit, char *name)
{
    uint32_t size;

    // setup file name if provided
    if (name) strcpy(rx.drv[unit].name, name);

    // set initial state from common block
    rx.drv[unit].ta  = rx.ta;
    rx.drv[unit].sa  = rx.sa;
    rx.drv[unit].len = rx.len;
    rx.drv[unit].pos = rx.pos;

    // zap the deleted data state to normal
    memset(rx.drv[unit].dd, RX_DATA_NORMAL, sizeof(rx.drv[unit].dd));

    // special file name of "NONE" (any capitalization) maps to an unmounted drive
    if (strcasecmp(rx.drv[unit].name, "NONE") == 0) {
        // no online disk
        rx.drv[unit].rdy = FALSE;
        rx.drv[unit].den = RX_DEN_SD;
        // return the name
        return rx.drv[unit].name;
    }

    // get size of (existing) file, check it for SD or DD or none; if not SD (RX01,RX02) or DD (RX02) then set it 
    size = sd_get_file_size(rx.drv[unit].name);
    if (rx.type == RX_TYPE_RX01) {
        // RX01 only supports SD disks
        if (size != rx_dsk_size(RX_DEN_SD))
            size = sd_set_file_size(rx.drv[unit].name, rx_dsk_size(RX_DEN_SD));
    } else {
        // RX02/RX03 supports SD or DD disks, default to DD
        if (size != rx_dsk_size(RX_DEN_SD) && size != rx_dsk_size(RX_DEN_DD))
            size = sd_set_file_size(rx.drv[unit].name, rx_dsk_size(RX_DEN_DD));
    }

    // set drive ready if file exists and is a right size (SD or DD)
    rx.drv[unit].rdy = (size == rx_dsk_size(RX_DEN_SD)) || (size == rx_dsk_size(RX_DEN_DD));

    // set drive density based on file size
    rx.drv[unit].den = (size == rx_dsk_size(RX_DEN_DD)) ? RX_DEN_DD : RX_DEN_SD;

    // return the name
    return rx.drv[unit].name;
}

// setup drive for unit using existing file name

char * rx_unit_file (uint8_t unit)
{
    return rx_unit_file(unit, NULL);
}



//
// setup the default emulation type (1=RX01, 2=RX02, etc)
//
uint8_t rx_emulation_type (uint8_t type)
{
    rx.type = rx_type = type;

    return rx.type;
}

uint8_t rx_emulation_type (void)
{
    return rx.type;
}



//
// setup the default timing type (0=fastest, 1=medium, 2=slow/normal)
//
uint8_t rx_timing_type (uint8_t type)
{
    rx.timing = type;

    return rx.timing;
}

uint8_t rx_timing_type (void)
{
    return rx.timing;
}



//
// setup the RX02 subsystem debug port
//
uint8_t rx_debug (HardwareSerial *serialPort, uint8_t level)
{
    // set debug serial port (or not)
    debugPort = serialPort;

    // and the debug level
    debugLevel = level;

    // and done
    return debugLevel;
}

uint8_t rx_debug (void)
{
    return debugLevel;
}



//
// print RX emulator state
//
void rx_print_state (HardwareSerial *prt)
{
    uint8_t i;
    char den[] = { 'S', 'D', 'Q' };

    // dump to user supplied port if not null
    if (prt == NULL) prt = debugPort;
    // else try debug port, else return
    if (prt == NULL) return;

    prt->printf(F("\n\n--- RX Emulator State Dump ---\n\n"));
    prt->printf(F("    i/f width = %d.\n"),   rx_get_bits());
    prt->printf(F("     i/f mode = %s\n"),    rx_tst_dma() ? "DMA" : "PIO");
    prt->printf(F("     i/f init = %o\n"),    rx_tst_init());
    prt->printf(F("      i/f run = %o\n"),    rx_tst_run());
    prt->printf(F("\n"));
    prt->printf(F("        rx.cs = %06o\n"),  rx.cs);
    prt->printf(F("        rx.es = %04o\n"),  rx.es);
    prt->printf(F("        rx.ec = %04o\n"),  rx.ec);
    prt->printf(F("        rx.wc = %03o\n"),  rx.wc);
    prt->printf(F("        rx.tc = %03o\n"),  rx.tc);
    prt->printf(F("        rx.ta = %03o\n"),  rx.ta);
    prt->printf(F("        rx.sa = %03o\n"),  rx.sa);
    prt->printf(F("       rx.pos = %lu.\n"),  rx.pos);
    prt->printf(F("       rx.len = %u.\n"),   rx.len);
    prt->printf(F("       rx.den = %cD\n"),   den[rx.den]);
    prt->printf(F("      rx.unit = %o\n"),    rx.unit);
    prt->printf(F("      rx.type = RX0%d\n"), rx.type);
    prt->printf(F("    rx.cmdcnt = %u.\n"),   rx.cmdcnt);
    prt->printf(F("    rx.errcnt = %u.\n"),   rx.errcnt);
    prt->printf(F("    rx.timing = %o\n"),    rx.timing);
    prt->printf(F("  rx.fcn.code = %o\n"),    rx.fcn.code);
    prt->printf(F("  rx.fcn.name = %s\n"),    rx.fcn.name);
    for (i = 0; i < RX_NUNITS; ++i) {
        prt->printf(F("\n"));
        prt->printf(F("    rx.drv[%d].name = '%s'\n"), i, rx.drv[i].name);
        prt->printf(F("     rx.drv[%d].rdy = %c\n"),   i, rx.drv[i].rdy ? 'Y' : 'N');
        prt->printf(F("     rx.drv[%d].den = %cD\n"),  i, den[rx.drv[i].den]);
        prt->printf(F("      rx.drv[%d].ta = %03o\n"), i, rx.drv[i].ta);
        prt->printf(F("      rx.drv[%d].sa = %03o\n"), i, rx.drv[i].sa);
        prt->printf(F("     rx.drv[%d].pos = %lu.\n"), i, rx.drv[i].pos);
        prt->printf(F("     rx.drv[%d].len = %u.\n"),  i, rx.drv[i].len);
    }
    prt->printf(F("\n"));

    return;
}



//
// execute an RX function ... the mainline RX emulator routine, executes one command
//
void rx_function (void)
{
    uint16_t i, j;
    uint16_t value;

    // setup for a new request
    rx_clr_request();
    rx_clr_out();
    rx_set_done();

    // setup INIT seen callback
    if (setjmp(rx_init_env)) {
        // return to here on longjmp(rx_init_env,N);
        rx_init_seen = 0;
        rx_initialize(false);
        return;
    }

    // wait for RUN or INIT; 25ms timeout if no RUN or INIT
    if (rx_wait_run_or_init(25) == RX_SAW_NONE) return;

    // RUN seen, process command ...

    // receive a command word: 12b on RX211 or RX8E/28 in 12b mode; 8b otherwise
    rx.cs = rx_recv(rx_tst_dma() || rx_tst_12b() ? 12 : 8);
    if (debugLevel) debugPort->printf(F("RX: cmd=%04o\n"), rx.cs);

    // led status
    led_state(red, off);
    led_state(green, on);
    led_state(yellow, off);

    // separate out the function field
    rx.fcn.code = (rx.cs & RXCS_FUNCTION)>>1;
    rx.fcn.name = fcn_name_list[rx.fcn.code];

    // separate out the unit number in the command
    rx.unit = (rx.cs & RXCS_UNITSEL) ? 1 : 0;

    // separate out the density flag in the command
    rx.den = (rx.cs & RXCS_DENSEL) ? RX_DEN_DD : RX_DEN_SD;

    // make selected unit ready, if exists
    rx_unit_file(rx.unit);

    // initial error code: success!
    rx.ec = RXERR_SUCCESS;

    // initial error status
    rx_init_es();

    // print command
    if (debugLevel) debugPort->printf(F("RX: %s unit=%o den=%o\n"), rx.fcn.name, rx.unit, rx.den);

    // decode command function
    switch (rx.fcn.code) {

    // === buffer fill or empty ===
    //
    // DMA: command(12), wordcount(8), N*databytes(8)
    // PIO: command(12), N*databytes(8 or 12)
    //
    case RXFCN_FILL:
    case RXFCN_EMPTY:

        // compute word count
        if (rx_tst_dma()) {
            // if DMA mode, receive word count from interface
            rx.wc = rx_recv_hs(8);
            // receive bus address
            // rx.ba = rx_recv_hs(16); // done in RX211 hardware
        } else {
            // if PIO mode, compute word count from full sector size
            rx.wc = rx_sec_size(rx.den) >> (rx_tst_8b() ? 1 : 2);
        }
        if (debugLevel) debugPort->printf(F("RX: %s wc=%03o\n"), rx.fcn.name, rx.wc);

        // transform word count to transfer count
        rx.tc = 2*rx.wc;

        // check for word count overflow (only can happen on RX211 dma interface with user word count)
        if (rx.tc > rx_sec_size(rx.den)) {
            if (debugLevel) debugPort->printf(F("RX: %s wc=%03o WCOVF\n"), rx.fcn.name, rx.wc);
            rx.ec = RXERR_WCOVF;
            rx.es |= RXES_WCOVF;
            goto error;
        }

        // transfer data words in/out
        if (rx.fcn.code == RXFCN_EMPTY) {
            // empty buffer (RX controller buffer to host interface)
            for (i = j = 0; i < rx.tc; ++i) {
                if (rx_tst_8b()) {
                    // extract 8b values from buffer to send as 8b
                    value = rx.buffer[i];
                    rx_xmit_hs(value, 8);
                } else {
                    // 12b values are packed into the first 2/3 of the sector; read 2 8b entries and build 12b value
                    j = 3*i/2;
                    if (i & 1) {
                        // odd byte
                        value = ((rx.buffer[j+0] & 017) << 8) | rx.buffer[j+1];
                    } else {
                        // even byte
                        value = (rx.buffer[j+0] << 4) | ((rx.buffer[j+1] >> 4) & 017);
                    }
                    rx_xmit_hs(value, 12);
                }
                if (debugLevel >= 2) {
                    if ((i & 7) == 0) debugPort->printf(F("RX: %s %03o:"), rx.fcn.name, i);
                    if (rx_tst_8b())  debugPort->printf(F(" %03o"), value); else debugPort->printf(F(" %04o"), value);
                    if ((i & 7) == 7) debugPort->printf(F("\n"));
                }
            } // for (i = 0; i < rx.tc; ++i)
            if (debugLevel >= 2 && (i & 7) != 0) debugPort->printf(F("\n"));
        } else {
            // fill buffer (host interface to RX controller buffer)
            for (i = j = 0; i < rx.tc; ++i) {
                if (rx_tst_8b()) {
                    // 8b values get stuffed into 8b buffer directly
                    value = rx_recv_hs(8);
                    rx.buffer[i] = value;
                    j = i+1;
                } else {
                    // 12b values get split into 4b/8b and merged into two 8b entries
                    value = rx_recv_hs(12);
                    j = 3*i/2;
                    if (i & 1) {
                        // odd byte
                        rx.buffer[j++] |= (value >> 8) & 017;
                        rx.buffer[j++] = value;
                    } else {
                        // even byte
                        rx.buffer[j++] = value >> 4;
                        rx.buffer[j++] = (value & 017) << 4;
                    }
                }
                if (debugLevel >= 2) {
                    if ((i & 7) == 0) debugPort->printf(F("RX: %s %03o:"), rx.fcn.name, i);
                    if (rx_tst_8b())  debugPort->printf(F(" %03o"), value); else debugPort->printf(F(" %04o"), value);
                    if ((i & 7) == 7) debugPort->printf(F("\n"));
                }
            } // for (i = 0; i < rx.tc; ++i)
            if (debugLevel >= 2 && (i & 7) != 0) debugPort->printf(F("\n"));
            if (debugLevel >= 2 && j < rx_sec_size(rx.den))
                debugPort->printf(F("RX: %s zero fill buffer %03o..%03o\n"), rx.fcn.name, j, rx_sec_size(rx.den)-1);
            // zero fill any unwritten bytes at the end of the buffer up to the sector size
            while  (j < rx_sec_size(rx.den)) rx.buffer[j++] = 0;
        }

        // indicate transfer is complete
        rx.wc = 0;
        break;

    // === read/write sector ===
    //
    // command(12), sector(8 or 12), track(8 or 12)
    //
    case RXFCN_RDSECT:
    case RXFCN_WRSECT:  
    case RXFCN_WRDDSECT:

        // first word is sector address (001..032 are valid)
        rx.sa = rx_recv_hs() & 0077;
        if (debugLevel) debugPort->printf(F("RX: %s sa=%03o\n"), rx.fcn.name, rx.sa);

        // second word is track address (000..114 are valid)
        rx.ta = rx_recv_hs() & 0377;
        if (debugLevel) debugPort->printf(F("RX: %s ta=%03o\n"), rx.fcn.name, rx.ta);

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, error
            goto error;
        }

        // check track access is valid
        if (rx.ta >= RX_NTRKS) {
            rx.ec = RXERR_TRKERR;
            goto error;
        }

        // check density matches, error if does not
        if (rx.den != rx.drv[rx.unit].den) {
            rx.ec = RXERR_DENERR;
            rx.es |= RXES_DENERR;
            goto error;
        }

        // check sector address is valid
        if (rx.sa < 1 || rx.sa > RX_NSECS) {
            rx.ec = RXERR_SECFAIL;
            goto error;
        }

        // compute length of transfer (sector size)
        rx.len = rx_sec_size(rx.drv[rx.unit].den);
        // compute byte offset into disk image
        rx.pos = (rx.ta*RX_NSECS + (rx.sa-1)) * rx.len;
        // print details if in debug mode
        if (debugLevel) debugPort->printf(F("RX: %s pos=%lu. len=%u.\n"), rx.fcn.name, rx.pos, rx.len);

        // bail on INIT seen
        if (rx_init_seen) { longjmp(rx_init_env, 20); }

        // indicate SD card access in progress
        led_state(yellow, on);

        // do the read or write
        if (rx.fcn.code == RXFCN_RDSECT) {
            // do a read; copy data into buffer from diskimage@offset
            value = sd_read_bytes(rx.drv[rx.unit].name, rx.pos, rx.buffer, rx.len);
        } else {
            // do a write
            if (rx.fcn.code == RXFCN_WRDDSECT) {
                bitWrite(rx.drv[rx.unit].dd[rx.ta], rx.sa, RX_DATA_DELETED);
            } else {
                bitWrite(rx.drv[rx.unit].dd[rx.ta], rx.sa, RX_DATA_NORMAL);
            }
            // copy data from rx.buffer[] to diskimage@offset
            value = sd_write_bytes(rx.drv[rx.unit].name, rx.pos, rx.buffer, rx.len);
        }
        // set deleted data status based on sector written or read deldata status
        if (bitRead(rx.drv[rx.unit].dd[rx.ta], rx.sa) == RX_DATA_DELETED) rx.es |= RXES_DELDATA;

        // SD card access complete
        led_state(yellow, off);

        // simulate timing
        rx_timing(RX_TIME_SEEK);

        // update state info for drive
        rx.drv[rx.unit].ta = rx.ta;
        rx.drv[rx.unit].sa = rx.sa;
        rx.drv[rx.unit].pos = rx.pos;
        rx.drv[rx.unit].len = rx.len;

        // check for read/write error
        if (value != rx.len) {
            rx.ec = RXERR_IDMFAIL;
            goto error;
        }

        // and done
        break;

    // === set media density ===
    //
    // command(12), key(8 or 12)
    //
    case RXFCN_SETMEDIA:

        // this function is a NOP for the RX01
        if (rx.type == RX_TYPE_RX01) break;

        // first word must be the magic key 'I'
        value = rx_recv_hs();
        // check key for expected value
        if (value != 'I') {
            rx.ec = RXERR_KEYERR;
            goto error;
        }

        // check drive is ready
        if (!rx.drv[rx.unit].rdy) {
            // nope, error
            goto error;
        }

        // simulate timing
        rx_timing(RX_TIME_SETMEDIA);

        // bail on INIT seen
        if (rx_init_seen) { longjmp(rx_init_env, 21); }

        // indicate SD card access in progress
        led_state(yellow, on);

        // change density to requested value AND always zero the entire disk image
        sd_set_file_size(rx.drv[rx.unit].name, rx_dsk_size(rx.den));

        // update unit density flag
        rx.drv[rx.unit].den = rx.den;

        // update status since may have changed unit density
        rx_init_es();

        // indicate SD card access complete
        led_state(yellow, off);

        // and done
        break;

    // === read status ===
    //
    // command(12)
    //
    case RXFCN_RDSTAT:

        // simulate timing
        rx_timing(RX_TIME_RDSTAT);

        // update error status (already done above)
        // rx_init_es();

        // and done
        break;

    // === read error code ===
    //
    // command(12)
    //
    case RXFCN_RDERROR:

        if (rx_tst_dma()) {

            // RX211/RXV21; return four 16b status words

            // simulate timing
            rx_timing(RX_TIME_RDERROR);

            // receive bus address
            // rx.ba = rx_recv_hs(16); // done in RX211 hardware

            // generate status byte
            value = (rx.unit == 1               ? (1<<7) : 0)
                  | (rx.drv[1].den == RX_DEN_DD ? (1<<6) : 0)
                  | (rx.drv[rx.unit].rdy        ? (1<<5) : 0)
                  | (rx.drv[0].den == RX_DEN_DD ? (1<<4) : 0)
                  | (rx.den == RX_DEN_DD        ? (1<<0) : 0);

            // print error block
            if (debugLevel >= 2) {
                debugPort->printf(F("RX: %s Word%d: %03o %03o\n"), rx.fcn.name, 1, rx.wc,              rx.ec);
                debugPort->printf(F("RX: %s Word%d: %03o %03o\n"), rx.fcn.name, 2, rx.drv[1].ta,       rx.drv[0].ta);
                debugPort->printf(F("RX: %s Word%d: %03o %03o\n"), rx.fcn.name, 3, rx.sa,              rx.ta);
                debugPort->printf(F("RX: %s Word%d: %03o %03o\n"), rx.fcn.name, 4, rx.drv[rx.unit].ta, value);
            }

            // word 1
            rx_xmit_hs(rx.ec, 8);
            rx_xmit_hs(rx.wc, 8);

            // word 2
            rx_xmit_hs(rx.drv[0].ta, 8);
            rx_xmit_hs(rx.drv[1].ta, 8);

            // word 3
            rx_xmit_hs(rx.ta, 8);
            rx_xmit_hs(rx.sa, 8);

            // word 4
            rx_xmit_hs(value, 8);
            rx_xmit_hs(rx.drv[rx.unit].ta, 8);

        } else if (rx.type == RX_TYPE_RX01) {

            // RX11/RXV11/RX8E; return EC register in DB

            // return EC instead of ES
            rx.es = rx.ec;

        }

        // and done
        break;

    } // switch (rx.fcn.num)

done:
    rx.cmdcnt++;
    rx_xmit_es();
    led_state(green, off);
    return;

error:
    rx.errcnt++;
    rx_set_error();
    led_state(red, on);
    goto done;
}



// the end
 
Last edited:
So, mega success. I was finally able to get my RX02 emulator passing all three DEC RX02/RX211 diagnostics:

(1) ZXRDC0 : RX02 SS PERF EXER (it has been doing this for a while)
(2) ZRXEA0 : RX02 FMTR PROG (just tonight!)
(3) ZRXFB0 : RX02 FUNCTION-LOGIC TEST (just tonight!)

ZXRF the RX02/RX211 hardware function/logic diagnostic is the big one. Fooling the DEC diagnostic with the RX02 emulator so that this test passes 100% turned out to be a non-trival debugging effort. Ultimately it came down to being able to set the appropriate bits in the RXES status register as the diagnostic expects them to be set by a real RX02.

Ultimately I found the correct solution after pouring thru the DEC RX02 microcode, the DEC RX02 Tech Manual, the DEC ZRXFB0 program listing, and the DSD 440 Tech Manual (a 3rd party RX01/02 compatible controller). One might think that all the bits in the RXES (returned after the completion of each function) might be set the same for each different controller function (ie, unit selected, selected unit density, etc). But one would be wrong. There is a lack of consistency in how these bits get set by an RX02 drive, and of course the DEC diagnostic (and some DEC software, like the ZRXExx Disk Formatter program) depend on this inconsistent behavior.

I tried doing some debugging using the RX02 simulation under PDP-11 SIMH, but alas it does not pass diagnostics either (and essentially has the same issues I discovered in how it handles the RXES status register). Maybe I'll go in and see if I can update the SIMH RX02 driver with the changes I discovered necessary to pass the diagnostic.

Following are the logs of running each of the three DEC diagnostic programs. Next I will move on to PDP11 O/S testing (RT11 at least, maybe 2.11BSD). And of course hardware debugging of the emulator on the RX8E/RX28 RX02 controller in my PDP-8m (should be lots more fun).

Don

*NEW* I built a bootable RT11 v5.7 RX02 image under SIMH, copied it to the Arduino MicroSD card, and booted in on my 11/44. So RT11 seems to be happy as well.

RT11 log:
Code:
>>>b dy1

(Program)

RT-11SB  V05.07

.R MSCPCK

.date
?KMON-W-No date

.da 21-jan-2000

.dir
 21-Jan-2000
SWAP  .SYS    28P 21-Jan-2000    RT11SB.SYS    97P 21-Jan-2000
RT11FB.SYS   106P 21-Jan-2000    DL    .SYS     4P 21-Jan-2000
DU    .SYS    10P 21-Jan-2000    DX    .SYS     4P 21-Jan-2000
DY    .SYS     4P 21-Jan-2000    DZ    .SYS     4P 21-Jan-2000
RK    .SYS     3P 21-Jan-2000    LD    .SYS    11P 21-Jan-2000
LP    .SYS     2P 21-Jan-2000    LS    .SYS     5P 21-Jan-2000
NL    .SYS     2P 21-Jan-2000    PI    .SYS    60P 21-Jan-2000
SL    .SYS    17P 21-Jan-2000    SP    .SYS     7P 21-Jan-2000
VM    .SYS     3P 21-Jan-2000    PIP   .SAV    30P 21-Jan-2000
DIR   .SAV    20P 21-Jan-2000    DUP   .SAV    52P 21-Jan-2000
RESORC.SAV    35P 21-Jan-2000    HELP  .SAV   161P 21-Jan-2000
FORMAT.SAV    28P 21-Jan-2000    DUMP  .SAV    10P 21-Jan-2000
DATIME.SAV     4P 21-Jan-2000    EDIT  .SAV    19P 21-Jan-2000
TECO  .SAV    50P 21-Jan-2000    STRTSB.COM     1P 21-Jan-2000
MSCPCK.SAV     4P 21-Jan-2000
 29 Files, 781 Blocks
 193 Free blocks

.sho all

RT-11SB  V05.07
Booted from DY1:RT11SB

USR     is set SWAP
EXIT    is set SWAP
KMON    is set NOIND
MODE    is set NOSJ
TT      is set NOQUIET
ERROR   is set ERROR
SL      is set OFF
EDIT    is set KED
FORTRAN is set FORTRA
KMON nesting depth is 3

CLI is set DCL, CCL, UCL, NO UCF

PDP 11/44 Processor
3840KB of memory
FP11 Hardware Floating Point Unit
Extended Instruction Set (EIS)
Commercial Instruction Set (CIS)
Memory Management Unit
ECC Memory
Cache Memory
60 Hertz System Clock
KW11-P User Programmable Clock

FPU support

Device    Status                   CSR     Vector(s)
------    ------                   ---     ---------
  DL      Not installed           174400   160
  DU      Installed               172150   154
  DX      Not installed           177170   264
  DY      Resident                177170   264
  DZ      Not installed           000000
  RK      Not installed           177400   220
  LD      Installed               000000   000
  LP      Not installed           177514   200
  LS      Installed               176500   470 474 300 304
  NL      Installed               000000   000
  PI      Not installed           000000   000
  SL      Installed               000000   000
  SP      Installed               000000   110
  VM      Installed               177572   250

TT  (Resident)
DY  (Resident)
    DY1 = DK , SY
LD
SL
DU
VM
SP
LS
NL
13 free slots

Job  Name  Console Level State    Low    High  Impure
---  ----  ------- ----- -----    ---    ----  ------
 0   RESORC   0      0   Run     000000 137010 140514

No multi-terminal support

Address   Module    Words
-------   ------    -----
160000    IOPAGE     4096.
156542    DY          335.
137054    RMON       3995.
001000    ..BG..    24086.

No LD units mounted

.

ZXRF log:
Code:
>>>b dd3

(Program)

BOOTING UP XXDP-XM EXTENDED MONITOR

XXDP-XM EXTENDED MONITOR - XXDP V2.5
REVISION: F0
BOOTED FROM DD3
124KW OF MEMORY
UNIBUS SYSTEM

RESTART ADDRESS: 152000
TYPE "H" FOR HELP !

.R ZRXF??
ZRXFB0.BIC

DRSSM-G2
CZRXFB0-0-0
RX02 FUNCTION-LOGIC TEST
UNIT IS RX02
RSTRT ADR 145702
DR>STA

CHANGE HW (L)  ? Y

# UNITS (D)  ? 2

UNIT 0
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ?
EXP WRD-CR (O)  0 ?
BR-LEVEL   (O)  5 ?

UNIT 1
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ? 1
EXP WRD-CR (O)  0 ?
BR-LEVEL   (O)  5 ?

CHANGE SW (L)  ? Y

TEST HELP  (L) N ? Y
DIAGNOSTIC MODES ARE:
      LOGIC TEST, FUNCTION TEST, OR BOTH
   -FUNCTION TESTS (1-10)
      ACT AS QUICK VERIFY & REPORT FAILING FUNCTIONS
   -LOGIC TESTS (11-36)
      ANALYZE FAILURE & GIVE ERROR INFO
      REPORT FIELD REPLACEABLE UNITS "FRU'S"
       ->DEVICE FATAL THRESHOLD LEVEL (DVTL) IS SET = 1
        "DVTL" = NO. OF HARD ERRS THAT CAUSE DEVICE FATAL ERR
TYPE "CR" TO CONTINUE (L) N ?
LOGIC TEST MODE    (L) Y ? Y
FUNCTION TEST MODE (L) N ? Y
HARD ERR -> DEVICE FATAL THRESHOLD LVL (O)  1 ?
NON-EXISTANT MEM ADR (NXM TST) (O)  160000 ?
EXTENDED ADR BITS: 13 & 12 (NPR-NXM TST) (O)  0 ?
TEST CONTROL FLAGS  (L) N ?
EXPANSION WORD TYPE <CR>  (L) N ?

 IS FLOPPY SYSTEM CONTAINING UNIT #00
  POWERED DOWN (L) N ? N

 IS FLOPPY SYSTEM CONTAINING UNIT #01
  POWERED DOWN (L) N ? N

CZRXFB0 EOP    1
    0 TOTAL ERRS

 IS FLOPPY SYSTEM CONTAINING UNIT #00
  POWERED DOWN (L) N ? N

 IS FLOPPY SYSTEM CONTAINING UNIT #01
  POWERED DOWN (L) N ? N

CZRXFB0 EOP    2
    0 TOTAL ERRS

(Console)
^P

>>>h
  Halted at 135642

ZXRE log:
Code:
>>>b dd3

(Program)

BOOTING UP XXDP-XM EXTENDED MONITOR

XXDP-XM EXTENDED MONITOR - XXDP V2.5
REVISION: F0
BOOTED FROM DD3
124KW OF MEMORY
UNIBUS SYSTEM

RESTART ADDRESS: 152000
TYPE "H" FOR HELP !

.R ZRXE??
ZRXEA2.BIC

CZRXEA0 RX02 FMTR PROG

HELP? (Y OR N) N  n
SET DISKETTE TO SINGLE DENSITY?   (Y OR N) N  n
VERIFY DISKETTE CRC (ALL TRACKS)?  (Y OR N) N  n
FLOPPY DISK SYSTEM: 0 ADDRESS CHANGE? (Y OR N) N n
IS ANOTHER FLOPPY DISK SYSTEM AVAILABLE? (Y OR N) N n

FORMAT DONE ON FOLLOWING
SYSTEM:0 DRIVE:0 DRIVE:1

FORMAT COMPLETED
DO YOU WANT TO FORMAT MORE DISKETTES? (Y OR N) N n
FORMATTER DONE-RESTART MONITOR OR UPDATE PROGRAM-->TYPE CTRL C TO
START THIS PROGRAM AGAIN
END OF PASS 0

(Console)
^P

>>>h
  Halted at 001442

ZXRD log:
Code:
>>>b dd3

(Program)

BOOTING UP XXDP-XM EXTENDED MONITOR

XXDP-XM EXTENDED MONITOR - XXDP V2.5
REVISION: F0
BOOTED FROM DD3
124KW OF MEMORY
UNIBUS SYSTEM

RESTART ADDRESS: 152000
TYPE "H" FOR HELP !

.R ZRXD??
ZRXDC0.BIC

DRSSM-G2
CZRXDC0-0-0
RX02 SS PERF EXER
UNIT IS RX02
RSTRT ADR 145702
DR>STA

CHANGE HW (L)  ? Y

# UNITS (D)  ? 2

UNIT 0
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ?
EXP WRD-CR (O)  0 ?

UNIT 1
RX BUS ADR (O)  177170 ?
VECTOR ADR (O)  264 ?
DRIVE #    (O)  0 ? 1
EXP WRD-CR (O)  0 ?

CHANGE SW (L)  ? Y

HELP TEST SETUP  (L) N ? Y
EXERCISE OPTIONS
   0 = WRITE-READ-DATA CK & READ-DATA CK
   1 = WRITE ONLY
   2 = WRITE-READ
   3 = WRITE-READ-DATA CHECK
   4 = READ-DATA CHECK ONLY
   5 = READ ONLY (CRC CHECK)
   6 = WRITE-READ-DATA CHECK ON ALTERNATE DRIVES
DATA PATTERN OPTIONS
   0 = RANDOM
   1 = ZEROS
   2 = ONES
   3 = FLOATING ZERO
   4 = FLOATING ONE
   5 = 125
   6 = 333
TRACK SEQUENCE OPTIONS
   0 = RANDOM
   1 = INCREMENT O.D.
   2 = DECREMENT I.D.
   3 = INCREMENT O.D.-DECREMENT I.D.
   4 = BOUNCE BETWEEN I.D. & O.D.
   5 = BOUNCE BETWEEN INCR. O.D. & DECR. I.D.
   6 = BOUNCE BETWEEN O.D. & DECR. I.D.
      (O.D. = OUTSIDE DIA. & I.D. = INSIDE DIA.)
->DEVICE FATAL THRESHOLD LVL=NO. OF HARD ERRS THAT CAUSE DEVICE FATAL ERR
  IF DRS "EVL" FLAG IS SET, BUT HARD ERR WILL STILL LOG AS A HARD ERR.
  THE "EVL" FLAG WILL CAUSE 10 RETRIED SOFT ERRS TO BECOME A HARD ERR
TYPE "CR" TO CONTINUE (L) N ?
EXERCISE #       (0-6) (O)  0 ? 3
DATA PATTERN #   (0-6) (O)  0 ? 0
TRACK SEQUENCE # (0-6) (O)  0 ? 0
DEVICE FATAL THRESHOLD LEVEL (D)  1 ?
RUN TEST IN DOUBLE DENSITY    (L) Y ?
RUN TEST IN DELETED DATA MODE (L) N ? Y
ANY PROGRAM CONTROL FLAGS     (L) N ?
MODIFY TRACK ADDRESS LIMITS   (L) N ?
MODIFY SECTOR ADDRESS LIMITS  (L) N ?
RXXX EXPANSION TYPE <CR>  (L) N ?
 UNIT#1- WRONG DENSITY -SINGLE DENSITY DISKETTE
          ->REFORMAT DISKETTE - ARE YOU SURE? (L) N ? Y
 UNIT#1-REFORMATTING, DO NOT INTERRUPT


                      UNIT#0       UNIT#1
# SECTOR READS  (8)=  00000004037  00000004037
# SECTOR WRITES (8)=  00000004037  00000004037

                    UNIT#0  UNIT#1
CHECK SUM:               0       0
FILL-EMP BUFF LOG:       0       0
NO ERR BIT:              0       0
INTER-NO DONE ERR:       0       0
INTERRUPT ERR:           0       0
SEEK:                    0       0
CRC ERR:                 0       0
CRC BAD:                 0       0
READ ERR:                0       0
WRITE ERR:               0       0
DATA ERR:                0       0
DEL. DATA ERR:           0       0
HRD SEEK:                0       0
HRD CRC ERR:             0       0
HRD CRC BAD:             0       0
HRD READ:                0       0
HRD WRITE:               0       0
HRD DATA:                0       0
HRD DEL. DATA ERR:       0       0

ERR
CODE#   UNIT#0  UNIT#1
010          0       0
020          0       0
030          0       0
040          0       0
050          0       0
060          0       0
070          0       0
100          0       0
110          0       0
120          0       0
130          0       0
140          0       0
150          0       0
160          0       0
170          0       0
200          0       0
210          0       0
220          0       0
230          0       0
240          0       0
250          0       0
260          0       0

TRACK#  UNIT#0  UNIT#1
  0          0       0
  1          0       0
  2          0       0
  3          0       0
  4          0       0
  5          0       0
  6          0       0
  7          0       0
  8          0       0
  9          0       0
 10          0       0
 11          0       0
 12          0       0
 13          0       0
 14          0       0
 15          0       0
 16          0       0
 17          0       0
 18          0       0
 19          0       0
 20          0       0
 21          0       0
 22          0       0
 23          0       0
 24          0       0
 25          0       0
 26          0       0
 27          0       0
 28          0       0
 29          0       0
 30          0       0
 31          0       0
 32          0       0
 33          0       0
 34          0       0
 35          0       0
 36          0       0
 37          0       0
 38          0       0
 39          0       0
 40          0       0
 41          0       0
 42          0       0
 43          0       0
 44          0       0
 45          0       0
 46          0       0
 47          0       0
 48          0       0
 49          0       0
 50          0       0
 51          0       0
 52          0       0
 53          0       0
 54          0       0
 55          0       0
 56          0       0
 57          0       0
 58          0       0
 59          0       0
 60          0       0
 61          0       0
 62          0       0
 63          0       0
 64          0       0
 65          0       0
 66          0       0
 67          0       0
 68          0       0
 69          0       0
 70          0       0
 71          0       0
 72          0       0
 73          0       0
 74          0       0
 75          0       0
 76          0       0
CZRXDC0 EOP    1
    0 TOTAL ERRS

^C
DR>EXIT
 
Last edited:
Back
Top