Jump to content
IGNORED

SIO Timing information?


Recommended Posts

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

Link to comment
Share on other sites

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)

  • Like 2
Link to comment
Share on other sites

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

 

  • Like 3
Link to comment
Share on other sites

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

Link to comment
Share on other sites

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;
  }
}

 

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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 *)&sector, 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;
  }
}

 

Link to comment
Share on other sites

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.

 

 

Link to comment
Share on other sites

#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

 

  • Like 2
Link to comment
Share on other sites

@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

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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.

Link to comment
Share on other sites

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

Link to comment
Share on other sites

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.

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

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

×   Your previous content has been restored.   Clear editor

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

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...