tschak909 Posted November 6, 2019 Share Posted November 6, 2019 While the Altirra Hardware Reference manual is excellent for gleaming low level SIO information, are there other good documents which show the SIO timing information? I am trying to get an ESP8266 to properly do SIO (and with standard arduino libraries, this is proving to be a serious pain in the (@#%@# ass) -Thom Quote Link to comment Share on other sites More sharing options...
Alfred Posted November 6, 2019 Share Posted November 6, 2019 The Atari hardware reference manual ? Mine's in a box somewhere, but it might show SIO timings. Quote Link to comment Share on other sites More sharing options...
Rybags Posted November 6, 2019 Share Posted November 6, 2019 I did a 1050 emulator on the ST with only the OS manual as reference. The important thing to realize is you're dealing with an old slow computer so such things as the minimum gap between command end and responding to it, and sending of Ack/Complete etc after an operation are important (Command end being when the line goes inactive) 2 Quote Link to comment Share on other sites More sharing options...
AtariGeezer Posted November 6, 2019 Share Posted November 6, 2019 These are the notes I took from the hardware manual: IHMEDIATE Sequence: ---+ +----------------------------------- COMMAND- | | +--------+ +------+ DATA OUT | cmnd | ----+frame +------------------------------------ +-+ +-+ DATA IN | | | | ---------------+ +----------//-----+ +---------- ACK CMPL || || | | | t0 t1 t2 t5 DATA SEND Sequence: ---+ +----------------------------------- COMMAND- | | +--------+ +------+ +---//---+ DATA OUT | cmnd | | data | ----+frame +------//--+ frame +---------------- +-+ +-+ +-+ DATA IN | | | | | | ---------------+ +-----------------+ +--//--+ +- ACK ACK CMPL || || | | | | | | | t0 t1 t2 t3 t4 t5 DATA RECEIVE Sequence: ---+ +------------------------------------ COMMAND- | | +--------+ +------+ DATA OUT | cmnd | ----+frame +------------------------------------- +-+ +-+ +----//----+ DATA IN | | | | | data | ----------------+ +--//--+ +-+ frame +-------- ACK CMPL || || | | | t0 t1 t2 t5 The computer generates a delay (tO) between the lowering of COMMAND- and the transmission of the first byte of the command frame. computer tO (min) = 750 microsec. computer tO (max) = 1600 microsec. peripheral tO (min) = ?? peripheral tO (max) = ?? The computer generates a delay (tl) between the transmission of the last bit of the command frame and the raising of the COMMAND- line. computer tl (min) = 650 microsec. computer tl (max) = 950 microsec. peripheral tl (min) = ?? peripheral tl (max) = ?? The peripheral generates a delay (t2) between the raising of COMMAND- and the transmission of the ACK byte by the peripheral. computer t2 (min) = O microsec. computer t2 (max) = 16 msec. peripheral t2 (min) = ?? peripheral t2 (max) = ?? The computer generates a delay (t3) between the receipt of the last bit of the ACK byte and the transmission of the first bit of the data frame by the computer. computer t3 (min) = 1000 microsec. computer t3 (max) = 1800 microsec. peripheral t3 (min> = ?? peripheral t3 <max> = ?? The peripheral generates a delay (t4) between the transmission of the last bit of the data frame and the receipt of the first bit of the ACK byte by the computer. computer t4 (min) = 850 microsec. computer t4 (max) = 16 msec. peripheral t4 (min) = ?? peripheral t4 (max) = ?? The Peripheral generates a delay (t5) between the the receipt of the last bit of the ACK byte and the first bit of the COMPLETE byte by the computer. computer t5 (min) = 250 microsec. computer t5 (max) = 255 sec. (handler-dependent) peripheral t5 (min) = ?? peripheral t5 (max) = N/A Communication First, the computer sets the COMMAND line low at the SIO connector. Then sends the Command Frame (4 bytes + checksum) Then waits for a response (1 byte without a checksum): $41 (A) = Acknowledge, the Command is valid and will be executed. $4E (N) = Negative, the Command is invalid. If the device responds with a "N" (NAK), the transfer is aborted. Otherwise ("A") and the Data Frame is transferred with the checksum. Now the computer waits for a final acknowledge (1 Byte without a checksum): $43 (C) = Complete, operation completed successfully. $45 (E) = an Error has occurred 3 Quote Link to comment Share on other sites More sharing options...
Rybags Posted November 7, 2019 Share Posted November 7, 2019 From memory I tried doing tighter timings at the emulation side and by going too far you'd end up with unstable IO, with the Atari missing frames and getting timeout errors (or requiring you to press Break for it to retry faster). 2 Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 7, 2019 Author Share Posted November 7, 2019 yeah, thanks, this is helpful. Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 7, 2019 Author Share Posted November 7, 2019 (edited) nm. I wrote something stupid. Edited November 7, 2019 by tschak909 Quote Link to comment Share on other sites More sharing options...
phaeron Posted November 7, 2019 Share Posted November 7, 2019 Note that the Atari OS doesn't quite adhere to the spec. Here's the XL/XE revision 2 OS violating the t1 minimum delay by deasserting COMMAND in <255 us instead of 650 us (stop bit is ~50us): 2 Quote Link to comment Share on other sites More sharing options...
Chilly Willy Posted November 7, 2019 Share Posted November 7, 2019 To be fair, deasserting COMMAND is the one thing that almost assuredly won't bite you in the rear for not meeting spec on. You're just telling the device "that's it, there's nothing else", so it really doesn't matter that you keep the device hanging on the edge of its seat waiting for another byte that isn't going to come only to be disappointed when you finally deassert COMMAND. ? Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 7, 2019 Author Share Posted November 7, 2019 Am losing my ($#@$(@# mind... #include <RemoteDebug.h> #include <RemoteDebugCfg.h> #include <RemoteDebugWS.h> #include <telnet.h> #include <ESP8266WiFi.h> #include <ESP8266WiFiAP.h> #include <ESP8266WiFiGeneric.h> #include <ESP8266WiFiMulti.h> #include <ESP8266WiFiScan.h> #include <ESP8266WiFiSTA.h> #include <ESP8266WiFiType.h> #include <WiFiClient.h> #include <WiFiClientSecure.h> #include <WiFiServer.h> #include <WiFiUdp.h> /** SIO test #5 */ RemoteDebug Debug; enum {ID, DEVICE, COMMAND, AUX1, AUX2, CHECKSUM, ACK, NAK, PROCESS, WAIT} cmdState = WAIT; #define PIN_LED 2 #define PIN_INT 5 #define PIN_PROC 4 #define PIN_MTR 16 #define PIN_CMD 12 union { struct { unsigned char devic; unsigned char comnd; unsigned char aux1; unsigned char aux2; unsigned char cksum; }; byte cmdFrameData[5]; } cmdFrame; /** calculate 8-bit checksum. */ byte sio_checksum(byte* chunk, int length) { int chkSum = 0; for (int i = 0; i < length; i++) { chkSum = ((chkSum + chunk[i]) >> 8) + ((chkSum + chunk[i]) & 0xff); } return (byte)chkSum; } /** ISR for falling COMMAND */ void sio_isr_cmd() { if (digitalRead(PIN_CMD) == LOW) { cmdState = ID; } else { cmdState = WAIT; } } void setup() { // Set up pins pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, HIGH); pinMode(PIN_INT, INPUT); pinMode(PIN_PROC, INPUT); pinMode(PIN_MTR, INPUT); pinMode(PIN_CMD, INPUT); // Set up serial Serial.begin(19200); Serial.swap(); // Attach COMMAND interrupt. attachInterrupt(digitalPinToInterrupt(PIN_CMD), sio_isr_cmd, CHANGE); WiFi.begin("Cherryhomes","e1xb64XC46"); Debug.begin("remotedebug"); Debug.showColors(true); } /** Get ID */ void sio_get_id() { if (Serial.available() > 0) cmdFrame.devic = Serial.read(); { if (cmdFrame.devic == 0x31) cmdState = COMMAND; else cmdState = WAIT; debugI("ID: 0x%02x",cmdFrame.devic); } } /** Get Command */ void sio_get_command() { if (Serial.available() > 0) { cmdFrame.comnd = Serial.read(); cmdState = AUX1; debugI("COMMAND: 0x%02x",cmdFrame.comnd); } } /** Get aux1 */ void sio_get_aux1() { if (Serial.available() > 0) { cmdFrame.aux1 = Serial.read(); cmdState = AUX2; debugI("AUX1: 0x%02x",cmdFrame.aux1); } } /** Get aux2 */ void sio_get_aux2() { if (Serial.available() > 0) { cmdFrame.aux2 = Serial.read(); cmdState = CHECKSUM; debugI("AUX2: 0x%02x",cmdFrame.aux2); } } /** Get Checksum, and compare */ void sio_get_checksum() { byte ck; if (Serial.available() > 0) { cmdFrame.cksum = Serial.read(); ck = sio_checksum((byte *)&cmdFrame.cmdFrameData, 4); if (ck == cmdFrame.cksum) { cmdState = ACK; debugI("CKSUM: 0x%02x ACK!",cmdFrame.cksum); } else { cmdState = NAK; debugI("CKSUM: 0x%02x NAK!",cmdFrame.cksum); } } } /** Send an acknowledgement */ void sio_ack() { Serial.write('A'); Serial.flush(); delay(2); debugI("Sending ACK"); cmdState = PROCESS; } /** Send a non-acknowledgement */ void sio_nak() { Serial.write('N'); Serial.flush(); delay(2); debugI("Sending NAK"); cmdState = WAIT; } /** Process a STATUS command */ void sio_status() { byte status[4] = {0x00, 0x00, 0xE0, 0x00}; // borrowed from an 810 byte ck = sio_checksum((byte *)&status, 4); Serial.write('C'); // COMPLETE Serial.flush(); delay(1); for (int i=0;i<4;i++) Serial.write(status[i]); delay(1); Serial.write(ck); // Write out the checksum debugI("Sent status"); } /** Process a read command */ void sio_read() { byte disk[128] = { 0x00, 0x01, 0x00, 0x07, 0x00, 0x07, 0xa9, 0x9a, 0x8d, 0xc4, 0x02, 0xa9, 0x45, 0x8d, 0xc8, 0x02, 0xa9, 0x31, 0x8d, 0x30, 0x02, 0xa9, 0x07, 0x8d, 0x31, 0x02, 0x4c, 0x1a, 0x07, 0x00, 0x00, 0x00, 0x25, 0x33, 0x30, 0x18, 0x12, 0x16, 0x16, 0x00, 0x22, 0x2f, 0x2f, 0x34, 0x25, 0x24, 0x00, 0x00, 0x00, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x47, 0x1d, 0x07, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte ck = sio_checksum((byte *)&disk, 128); Serial.write('C'); Serial.flush(); delay(1); Serial.write(disk, 128); Serial.write(ck); debugI("Sent read"); } /** Process command */ void sio_process_command() { debugI("PROCESS"); switch (cmdFrame.comnd) { case 'R': debugI("Calling read"); sio_read(); break; case 'S': debugI("Calling status"); sio_status(); break; } cmdState = WAIT; // wait for next command. } void loop() { Debug.handle(); switch (cmdState) { case ID: sio_get_id(); break; case COMMAND: sio_get_command(); break; case AUX1: sio_get_aux1(); break; case AUX2: sio_get_aux2(); break; case CHECKSUM: sio_get_checksum(); break; case ACK: sio_ack(); break; case NAK: sio_nak(); break; case PROCESS: sio_process_command(); break; case WAIT: break; } } Quote Link to comment Share on other sites More sharing options...
Rybags Posted November 7, 2019 Share Posted November 7, 2019 The /COMMAND thing is entirely at the host end. Stuff that can bite you from the peripheral end is sending the Ack and other status, or the data frame too early. That said, you'd probably want to employ some timeout and unusual condition handling at the peripheral end as well. Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 7, 2019 Author Share Posted November 7, 2019 The /COMMAND thing is entirely at the host end. Stuff that can bite you from the peripheral end is sending the Ack and other status, or the data frame too early. That said, you'd probably want to employ some timeout and unusual condition handling at the peripheral end as well.Timing under arduino seems damned near impossible. Turning on remote debug wildly changes timing enough to give completely different behavior.Sigh. Can i borrow a logic analyzer from someone?Sent from my SM-G920F using Tapatalk Quote Link to comment Share on other sites More sharing options...
Rybags Posted November 7, 2019 Share Posted November 7, 2019 Timing? As in relying on cycle counts of delay loops etc? Do you have some sort of external timer you can rely on? At the least shouldn't there be something that forms the basis of the ~ 19.2k bitrate (noting that in reality it's a bit less) Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 Getting closer, I'm wondering what I am doing wrong? I am obviously being stuck somewhere in my state machine, as doing a RESET on the unit puts it back on the road to sanity... With some bits from Mozzwald's changes to test #4, The latest code has been pushed, but for reference: /** SIO Test #5 - Implement as a FSM */ #include <FS.h> enum {ID, COMMAND, AUX1, AUX2, CHECKSUM, ACK, NAK, PROCESS, WAIT} cmdState; #define PIN_LED 2 #define PIN_INT 5 #define PIN_PROC 4 #define PIN_MTR 16 #define PIN_CMD 12 #define DELAY_T5 600 union { struct { unsigned char devic; unsigned char comnd; unsigned char aux1; unsigned char aux2; unsigned char cksum; }; byte cmdFrameData[5]; } cmdFrame; File atr; /** calculate 8-bit checksum. */ byte sio_checksum(byte* chunk, int length) { int chkSum = 0; for (int i = 0; i < length; i++) { chkSum = ((chkSum + chunk[i]) >> 8) + ((chkSum + chunk[i]) & 0xff); } return (byte)chkSum; } /** ISR for falling COMMAND */ void sio_isr_cmd() { if (digitalRead(PIN_CMD) == LOW) { cmdState = ID; } else { cmdState = WAIT; } } /** Get ID */ void sio_get_id() { while (Serial.available() == 0) { } if (Serial.available() > 0) cmdFrame.devic = Serial.read(); { if (cmdFrame.devic == 0x31) cmdState = COMMAND; else cmdState = WAIT; } } /** Get Command */ void sio_get_command() { while (Serial.available() == 0) { } if (Serial.available() > 0) { cmdFrame.comnd = Serial.read(); cmdState = AUX1; } } /** Get aux1 */ void sio_get_aux1() { while (Serial.available() == 0) { } if (Serial.available() > 0) { cmdFrame.aux1 = Serial.read(); cmdState = AUX2; } } /** Get aux2 */ void sio_get_aux2() { while (Serial.available() == 0) { } if (Serial.available() > 0) { cmdFrame.aux2 = Serial.read(); cmdState = CHECKSUM; } } /** Get Checksum, and compare */ void sio_get_checksum() { byte ck; while (Serial.available() == 0) { } if (Serial.available() > 0) { cmdFrame.cksum = Serial.read(); ck = sio_checksum((byte *)&cmdFrame.cmdFrameData, 4); if (ck == cmdFrame.cksum) { cmdState = ACK; } else { cmdState = NAK; } } } /** Send an acknowledgement */ void sio_ack() { delayMicroseconds(800); Serial.write('A'); Serial.flush(); cmdState = PROCESS; } /** Send a non-acknowledgement */ void sio_nak() { delayMicroseconds(800); Serial.write('N'); Serial.flush(); cmdState = WAIT; } /** Read */ void sio_read() { byte ck; byte sector[128]; int offset =(256 * cmdFrame.aux2)+cmdFrame.aux1; offset *= 128; offset -= 128; atr.seek(offset, SeekSet); atr.read(sector, 128); ck = sio_checksum((byte *)§or, 128); delayMicroseconds(600); // t5 delay Serial.write('C'); // Completed command // Write data frame Serial.write(sector,128); // Write data frame checksum Serial.write(ck); delayMicroseconds(200); } /** Status */ void sio_status() { byte status[4] = {0x00, 0xFF, 0xFE, 0x00}; byte ck; ck = sio_checksum((byte *)&status, 4); delayMicroseconds(600); // t5 delay Serial.write('C'); // Command always completes. delay(1); // Write data frame for (int i = 0; i < 4; i++) Serial.write(status[i]); // Write checksum Serial.write(ck); delayMicroseconds(200); } /** Process command */ void sio_process() { switch (cmdFrame.comnd) { case 'R': sio_read(); break; case 'S': sio_status(); } cmdState = WAIT; } void setup() { SPIFFS.begin(); atr=SPIFFS.open("/autorun.atr","r"); // Set up pins pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, HIGH); pinMode(PIN_INT, INPUT); pinMode(PIN_PROC, INPUT); pinMode(PIN_MTR, INPUT); pinMode(PIN_CMD, INPUT); // Set up serial Serial.begin(19200); Serial.swap(); // Attach COMMAND interrupt. attachInterrupt(digitalPinToInterrupt(PIN_CMD), sio_isr_cmd, CHANGE); } void loop() { switch (cmdState) { case ID: sio_get_id(); break; case COMMAND: sio_get_command(); break; case AUX1: sio_get_aux1(); break; case AUX2: sio_get_aux2(); break; case CHECKSUM: sio_get_checksum(); break; case ACK: sio_ack(); break; case NAK: sio_nak(); break; case PROCESS: sio_process(); break; case WAIT: break; } } Quote Link to comment Share on other sites More sharing options...
phaeron Posted November 8, 2019 Share Posted November 8, 2019 Deassert /COMMAND time can matter because of devices that check the command line state at the end of the command to see whether it is a valid command. Deassert too early from the computer side, and an XF551 will reject the command. Depend on 600us on the device side, and you'd be surprised. But I see that's not going on here. I'm not familiar with ESP8266 APIs, but looking at the code above, two things come to mind. First, the command ISR seems to force the state machine to WAIT state when COMMAND deasserts, which might conflict with the command processing. Second, the delay calls after serial writes might be shorter than expected if the serial port is buffered. Typically there is at least one buffered byte behind the out shift register to allow for continuous transmission, so this would allow the delay to execute two bytes early. The SIO delays are specified from the end of the last byte of each section, so waiting for transmission complete is needed to time them properly. Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 I can flush the TX buffers if needed... Also, yeah, I'm going to remove the WAIT in the ISR. That seems stupid in retrospect. -Thom Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 and that fixed it. holy crap. Jumpman booted. @phaeron if I ever meet you, I'd like to give you a hug. -Thom Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 #AtariWiFi I thought I was seriously done for the night, Phaeron (Avery Lee) made a critical observation on the Interrupt Service Routine (ISR) that is listening for COMMAND pin changes, one fix later (removing a superfluous state change that was causing a race condition), and Jumpman successfully boots off the ESP8266! Onward! https://www.youtube.com/watch?v=i4b0jA51fiI 2 Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 @phaeron am curious, I know I will at least need to support type 0 polling. This will allow for disk emulation to work, as well as being able to inject handlers in place in the event of no disk drives. I also understand if I want to utilize the AUTORUN.SYS handler, I'll need to implement type 1 polling. Ok, so far, so good. Are there any examples of implementations of the type 3/4 relocatable handler polling? Is it worth implementing it, for the XL/XE systems? or should I just say fuck it, and concentrate on developing a self-relocating handler? At the end of the day, I am going to try and implement the following devices: "D : " for disk emulation "R : " for RS232 (internet modem emulation) "N : " for a CIO interface to do TCP/UDP/HTTP type stuff I'd like to be able to select which of those bits of functionality to load, so that memory can be conserved, etc. (actually it makes me wonder if a sort of stub could be loaded into place so that if a device is accessed then the appropriate handler is sent over and loaded into place... meh, probably not) -Thom Quote Link to comment Share on other sites More sharing options...
evilmoo Posted November 8, 2019 Share Posted November 8, 2019 Maybe this is a kooky suggestion, but maybe it makes more sense to use the INTERRUPT or PROCEED line to signal that serial data is waiting, and then have the CPU poll for a block of data. Then the ESP8266 could buffer the data as necessary, and there would be fewer flow control issues. Quote Link to comment Share on other sites More sharing options...
Rybags Posted November 8, 2019 Share Posted November 8, 2019 Those lines aren't supported by the OS other than the stub IRQ handler which just clears the status and returns. Realistically they're not really needed for stock IO. But would be handy in a situation where a peripheral might want the computer's attention, e.g. remote control or a simple local network. Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 I'm going to try and utilize the proceed/interrupt lines to implement flow control while in modem mode. maybe in N: mode to indicate that there is something waiting in the receive buffer. -Thom Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 8, 2019 Author Share Posted November 8, 2019 18 minutes ago, evilmoo said: Maybe this is a kooky suggestion, but maybe it makes more sense to use the INTERRUPT or PROCEED line to signal that serial data is waiting, and then have the CPU poll for a block of data. Then the ESP8266 could buffer the data as necessary, and there would be fewer flow control issues. As for anyone who wants to hack on this, grab the code and mess with it, see what you can do with it. It's basically a self contained arduino sketch, and will stay that way, to encourage hacking. -Thom Quote Link to comment Share on other sites More sharing options...
phaeron Posted November 9, 2019 Share Posted November 9, 2019 I wouldn't bother implementing type 3 or 4 polling. No device that I know of supports them, no software I know of supports type 4 loaded handlers, and only the XL/XE OS issues them. I had to write a custom emulation device to test the type 3/4 poll and peripheral handler support in AltirraOS. Load-on-demand is of limited usefulness because of the difficulty of dynamic memory allocation in the Atari OS. Loading low is the more common strategy, but relies on programs being flexible with regard to MEMLO, which is mainly BASIC. Loading high is problematic because the screen handler itself wants to dynamically allocate downward and breaks if its allocation base is moved by a non-multiple of 4K. Quote Link to comment Share on other sites More sharing options...
tschak909 Posted November 10, 2019 Author Share Posted November 10, 2019 Noted. Ok. I will just do type 0 and 1. Right now, am working through a test program that talks TNFS (a simple network file system protocol) so I can try loading disks over the network. (This will be expanded out into full "D:" emulation) -Thom Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.