amurison718
Published © GPL3+

Atari 8 Bit MP3 Player

This paper examines the Atari's SIO protocol with a view to making it accessible for a newbie.

BeginnerProtip1,421
Atari 8 Bit MP3 Player

Things used in this project

Story

Read more

Code

Arduino Code

C/C++
// include SPI, MP3 and SD libraries
#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>

// Replies to be sent to the Atari
const byte ACK      = 0x41;
const byte NAK      = 0x4E;
const byte COMPLETE = 0x43;
const byte ERR      = 0x45;

// Sector size is 128 bytes
const unsigned long MAX_SECTOR_SIZE = 128;

// Commands received from the Atari
const byte CMD_FORMAT           = 0x21;
const byte CMD_FORMAT_MD        = 0x22;
const byte CMD_POLL             = 0x3F;
const byte CMD_PUT              = 0x50;
const byte CMD_READ             = 0x52;
const byte CMD_STATUS           = 0x53;
const byte CMD_WRITE            = 0x57;

// Other
const unsigned long READ_CMD_TIMEOUT     = 500;
const unsigned long READ_FRAME_TIMEOUT   = 2000;
const byte DELAY_T2 = 1;
const byte DELAY_T3 = 2;
const byte DELAY_T4 = 1;
const byte DELAY_T5 = 1;

// Whizzosoftware allows for up to 8 drives and an 850 module.
// Modified to include a printer
const byte DEVICE_D1            = 0x31;
const byte DEVICE_D2            = 0x32;
const byte DEVICE_D3            = 0x33;
const byte DEVICE_D4            = 0x34;
const byte DEVICE_D5            = 0x35;
const byte DEVICE_D6            = 0x36;
const byte DEVICE_D7            = 0x37;
const byte DEVICE_D8            = 0x38;
const byte PRINTER              = 0x40;
const byte DEVICE_R1            = 0x50;


#define PIN_ATARI_CMD         2   // the Atari SIO command line 
#define SIO_UART     Serial3
#define SIO_CALLBACK serialEvent3


// These are the pins used for the music maker shield
#define SHIELD_RESET  -1      // VS1053 reset pin (unused!)
#define SHIELD_CS     7      // VS1053 chip select pin (output)
#define SHIELD_DCS    6      // VS1053 Data/command select pin (output)

// These are common pins between breakout and shield
#define CARDCS 4     // Card chip select pin

// DREQ should be an Int pin, see 
#define DREQ 3       // VS1053 Data request, ideally an Interrupt pin

Adafruit_VS1053_FilePlayer musicPlayer =  Adafruit_VS1053_FilePlayer(SHIELD_RESET, SHIELD_CS, SHIELD_DCS, DREQ, CARDCS);
  
// variable used to produce the directory listing
String filelist;
byte dirlist;
byte* pdirlist;
String playfile;

//Command frame structure
const byte COMMAND_FRAME_SIZE   = 5;
struct CommandFrame {
  byte deviceId;
  byte command;
  byte aux1;
  byte aux2;
  byte checksum;
};


// variables used in serial communication
int m_cmdPinState;
int m_cmdPin;
Stream* m_stream;
unsigned long     m_startTimeoutInterval;
CommandFrame      m_cmdFrame;
byte*             m_cmdFramePtr;
byte              m_sectorBuffer[MAX_SECTOR_SIZE + 1];
byte*             m_putSectorBufferPtr;
int               m_putBytesRemaining;

void resetCommandFrameBuffer();
void dumpCommandFrame();
byte processCommand();
byte checksum(byte* chunk, int length);
void cmdPutSector(int deviceId);
void doPutSector();
boolean isChecksumValid();
boolean isCommandForThisDevice();
boolean isValidCommand();
boolean isValidDevice(byte b);
boolean isValidAuxData();
void cmdGetStatus();
void printDirectory(File dir);
void cmdGetSector(int deviceId);


void setup() {

  // initialize serial port to Atari
  SIO_UART.begin(19200);
  Serial.begin(19200); // does not like different baud rates
  Serial.println("Go!");

  pinMode(PIN_ATARI_CMD, INPUT);
  m_cmdPinState = 1; // initial state
  m_cmdPin = PIN_ATARI_CMD;

  Serial.println("Adafruit VS1053 Simple Test");

  if (! musicPlayer.begin()) { // initialise the music player
     Serial.println(F("Couldn't find VS1053, do you have the right pins defined?"));
     while (1);
  }
  Serial.println(F("VS1053 found"));
  
   if (!SD.begin(CARDCS)) {
    Serial.println(F("SD failed, or not present"));
    while (1);  // don't do anything more
  }

  // list files
  printDirectory(SD.open("/"));
  
  // Set volume for left, right channels. lower numbers == louder volume!
  musicPlayer.setVolume(20,20);


  // If DREQ is on an interrupt pin (on uno, #2 or #3) we can do background
  // audio playing
  musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);  // DREQ int
  
}

void loop() {

  switch (m_cmdPinState) {
    case 1:
      // initial state
      if (digitalRead(m_cmdPin) == HIGH)
      {
        m_cmdPinState = 2;
      }
      break;
    case 2:
      // waiting for start
      if (digitalRead(m_cmdPin) == LOW) {
        m_cmdPinState = 3;
        resetCommandFrameBuffer();
        Serial.println("Cmd pin low");
      }
      break;
    case 3:
      // Read command
      m_startTimeoutInterval = millis();
      // if command frame is fully read...
      if (m_cmdFramePtr - (byte*)&m_cmdFrame == COMMAND_FRAME_SIZE)
      {
        Serial.println("Frame Read");
        dumpCommandFrame();
        // process command frame
        if (isChecksumValid() && isCommandForThisDevice())
        {
          Serial.println("For this device and checksum okay");
          if (isValidCommand() && isValidAuxData())
          {
            m_cmdPinState = processCommand();
            Serial.print("New cmd status ");
            Serial.println(m_cmdPinState);
          }
          else
          {
            m_cmdPinState = 2; //wait for start
            Serial.println("Invalid Command");
          }
        }
        else
        {
          m_cmdPinState = 2; //wait for start
          Serial.println("Not this device or checksum error");
        }
        // otherwise, check for command read timeout
      }
      else if (millis() - m_startTimeoutInterval > READ_CMD_TIMEOUT)
      {
        m_cmdPinState = 2; //wait for start
        Serial.println("Time out");
      }
      break;
    case 4:
      // Read data frame
      // Serial.println("Read data frame");
      // check for timeout
      if (millis() - m_startTimeoutInterval > READ_FRAME_TIMEOUT) {
        m_cmdPinState = 2; //wait for start
      }
      break;
    case 5:
      // wait for end

      if (digitalRead(m_cmdPin) == HIGH)
      {
        m_cmdPinState = 2; // wait for start
        Serial.println("Wait for end done");
      }
      break;
  }
}



void SIO_CALLBACK()
{
  // read the next byte from the bus
  byte b = SIO_UART.read();


  switch (m_cmdPinState) {
    // if we read a valid device byte and are in a "command wait" state, come out of it and
    // process the byte
    case 1: //initial state
    case 2: // wait for command
    case 5: // wait for end
      if (digitalRead(m_cmdPin) == LOW && isValidDevice(b)) {
        m_cmdPinState = 3; // Read
        resetCommandFrameBuffer();
      }
      else
      {
        break;
      }
    // if we're reading a command frame...
    case 3: //read
      {
        // read the data into the command frame
        // sometimes we see extra bytes between command frames on the bus while reading a command and things get lost --
        // the isValidDevice() check prevents a command frame read from getting corrupted by them

        int idx = (int)m_cmdFramePtr - (int)&m_cmdFrame;
        if (idx < COMMAND_FRAME_SIZE && (idx > 0 || (idx == 0 && isValidDevice(b)))) {
          *m_cmdFramePtr = b;
          m_cmdFramePtr++;
          return;
        }
        break;
      }
    // if we're reading a data frame...
    case 4:
      {
           // add byte to read sector buffer
          *m_putSectorBufferPtr = b;
          m_putSectorBufferPtr++;
          m_putBytesRemaining--;

          if (m_putBytesRemaining == 0 || b == 0x9b)
          {
            doPutSector();
          }
        break;
      }
    default:

      break;
  }
}

void cmdPutSector(int deviceId) {
  // send ACK
  delay(DELAY_T2);
  SIO_UART.write(ACK);
  Serial.println("Put sector ACK");

  m_putBytesRemaining = MAX_SECTOR_SIZE;
  m_putSectorBufferPtr = m_sectorBuffer;

}

void doPutSector()
{
  int sectorSize = m_putSectorBufferPtr - m_sectorBuffer - 1;
 int j;

  // clear playfile
      playfile = "";  
 
  // send ACK
  delay(2);

  SIO_UART.write(ACK);
  Serial.println("doPutSector ACK");

 for (j=0; j <2; j++)
 {
 delay(1);

  // send COMPLETE
  SIO_UART.write(COMPLETE);
 }
  Serial.println("doPutSector complete");

  // get filename
  for (int i = 0; i < sectorSize; i++)
  {
    playfile += (char)m_sectorBuffer[i];
  }

  // change state
  m_cmdPinState = 2; //wait for start


  
// check for repeated frame buffer
    if (playfile[0] == m_cmdFrame.deviceId &&
        playfile[1] == m_cmdFrame.command) 
       {
       playfile = playfile.substring(5, playfile.length());
       }
       
   
  musicPlayer.startPlayingFile(playfile.c_str());
  }


boolean isChecksumValid() {
  byte chkSum = checksum((byte*)&m_cmdFrame, 4);
  if (chkSum != m_cmdFrame.checksum)
  {
    Serial.println("Checksum fail");
    return false;
  }
  else
  {
    Serial.println("Checksum pass");
    return true;
  }
}

boolean isCommandForThisDevice() {

boolean result = (m_cmdFrame.deviceId == PRINTER ||
                  m_cmdFrame.deviceId == DEVICE_D2);

  return result;
}

boolean isValidCommand() {
  boolean result = (m_cmdFrame.command == CMD_READ ||
                    m_cmdFrame.command == CMD_WRITE ||
                    m_cmdFrame.command == CMD_STATUS ||
                    m_cmdFrame.command == CMD_PUT ||
                    m_cmdFrame.command == CMD_FORMAT ||
                    m_cmdFrame.command == CMD_FORMAT_MD);

  if (!result) {
    result = 0;
  }

  return result;
}

boolean isValidDevice(byte b) {
  boolean result = (b == DEVICE_D1 ||
                    b == DEVICE_D2 ||
                    b == DEVICE_D3 ||
                    b == DEVICE_D4 ||
                    b == DEVICE_D5 ||
                    b == DEVICE_D6 ||
                    b == DEVICE_D7 ||
                    b == DEVICE_D8 ||
                    b == PRINTER   ||
                    b == DEVICE_R1);

  if (!result) {
    result = 0;
  }

  return result;
}

boolean isValidAuxData() {
  return true;
}


void cmdGetStatus(int deviceId)
{
  byte b[4] = {0, 0, 2, 0};
  byte chksum;

  chksum = checksum((byte*)b, 4);
  // send ACK
  delay(DELAY_T2);
  SIO_UART.write(ACK);
  Serial.println("ACK");

  // send complete
  delay(DELAY_T5);
  SIO_UART.write(COMPLETE);
  Serial.println("Get status COMPLETE");


  // send status to bus
  for (int i = 0; i < 4; i++) {
    SIO_UART.write(b[i]);
  }

  SIO_UART.write(chksum);

}



byte checksum(byte* chunk, int length) {
  int chkSum = 0;
  for (int i = 0; i < length; i++) 
  {
    chkSum = chkSum + chunk[i];
     if (chkSum >= 256)
      {
        chkSum = (chkSum -256) +1;
      }  

  }
  return (byte)chkSum;
}



byte processCommand() {
  int deviceId = 1;
  byte nextCmdPinState = 5; // wait for end

  switch (m_cmdFrame.command) {
    case CMD_READ:
      cmdGetSector(deviceId);
      break;
    case CMD_PUT:
    case CMD_WRITE:
      cmdPutSector(deviceId);
      nextCmdPinState = 4; // read data frame
      m_startTimeoutInterval = millis();
      break;
    case CMD_STATUS:
      cmdGetStatus(deviceId);
      break;
    case CMD_FORMAT:
      //cmdFormat(deviceId, DENSITY_SD);
      break;
    case CMD_FORMAT_MD:
      //cmdFormat(deviceId, DENSITY_ED);
      break;
    default:
      //m_sdriveHandler.processCommand(&m_cmdFrame, m_stream);
      break;
  }

  return nextCmdPinState;
}


void dumpCommandFrame() {

  Serial.println(m_cmdFrame.deviceId, HEX);
  Serial.println(m_cmdFrame.command, HEX);
  Serial.println(m_cmdFrame.aux1, HEX);
  Serial.println(m_cmdFrame.aux2, HEX);
  Serial.println(m_cmdFrame.checksum, HEX);

}


void resetCommandFrameBuffer() {
  // reset last command frame info
  memset(&m_cmdFrame, 0, sizeof(m_cmdFrame));
  m_cmdFramePtr = (byte*)&m_cmdFrame;
  Serial.println("buffer reset");
}



void printDirectory(File dir) 
{
 String tempName;  
 int i;
 int dot;
 String fileHeader = "00000";

   while(true) {
     
     File entry =  dir.openNextFile();
     if (! entry) 
  {
       // no more files
       //Serial.println("**nomorefiles**");
       break;
     }

     if (!entry.isDirectory()) 
  {

 // each entry needs to be in the format 5 digits 8 characters 3 ext (16 chrs long)
    filelist += fileHeader;
    tempName = entry.name();
    dot = tempName.indexOf(".");

    filelist += tempName.substring(0, dot);

     if (dot<8)
     {
      for (i=0; i<(8-dot); i++)
      {
        filelist += " ";
      }
     }

     filelist += tempName.substring(dot+1, tempName.length());

     } 
     entry.close();
   }

  
 // convert to byte array
  pdirlist = &dirlist; 
  for (i =0; i<129; i++)
  {
   if (filelist.length()>i)
    {
    *pdirlist = (byte)filelist[i];
    }
    else
    {
     *pdirlist = 0;
    }
    pdirlist ++;
   }
}


void cmdGetSector(int deviceId) {
 
int i;

 // send ACK
  delay(2);
  SIO_UART.write(ACK);
  Serial.println("Get sector ACK");

  // write data frame + checksum
  // send complete
  delay(1);
  SIO_UART.write(COMPLETE);
  Serial.println("Get sector COMPLETE");
  SIO_UART.flush();

  Serial.println("Directory");
  
    for (i=0; i<128; i++)
  {
     SIO_UART.write((byte)filelist[i]);
  }


    // write checksum
   byte chksum = checksum(&dirlist, filelist.length());
   SIO_UART.write(chksum);
   Serial.print("Get sector checksum ");
   Serial.println(chksum);


  SIO_UART.flush();
}

Credits

amurison718
4 projects • 2 followers
Contact

Comments

Please log in or sign up to comment.