Arduino Based SD Card Storage for the Acorn System 1

emulators, hardware and classic software for atom + system machines
Post Reply
Tel
Posts: 8
Joined: Sat Mar 16, 2019 3:49 pm
Contact:

Arduino Based SD Card Storage for the Acorn System 1

Post by Tel » Mon Jun 15, 2020 11:39 am

This post will chart the progress (or otherwise) of my attempts to replace the Acorn System 1 (AS1) cassette interface with an Arduino based SD card storage device.

Apologies for this initial ramble, but I think it's important to set the scene for this little project.

I dont know about anyone else, but I enjoy writing code. Especially code that actually does something and has a practical use. Oh - and the simpler to use, the better.

I hate having to back things up using methods such as the good old cassette interface. I realise that the nostalgia hit is real - especially when you hear the burst of two-tone that indicates a save / load is in progress. But it is painful to use, slow, and not always reliable.

Unless you upgrade your AS1 to one of the larger systems (S2-5), there aren't a lot of alternatives for data storage.

I have seen posts talking about the use of audio files to store the tape data and playing these back via a sound output of a desktop machine. That's really neat. But it's not what I want to do. I dont want to have to rely on a PC or Mac.

I want a proper digital solution that will be easy to use, and ideally self contained so that you dont need a desktop machine to handle the donkey work.
You can save data to the device, in a named file, or load a named file and transfer it to the AS1.

So thats my little project. It will act as a good refresher of 6502 assembler and a good introduction to the AS1.

I've played briefly with assorted PICs and so on over the years, and recently had a go with the Arduino. It's a neat little thing to use, provided you have something you actually want to do with it. For those of you who have never looked into them, they are basically a cheap processor card the size of a fag packet, with lots of IO. You programme them using a simplified C like language. They have lots of add-ons such as an SD card interface and loads of pre-written libraries of code to get things working.

I'm going to be using an Arduino Mega 2560 (because thats the one I bought a year or so ago) with a micro-SD card interface and (eventually) a touch screen. The total cost should be under £40. The SD card interface library handles up to a 16Gb micro-SD card. That will be enough programme storage for anyones AS1.

The initial phase will be to use an Arduino as a 'digital' recorder to receive and playback data using the AS1 cassette subroutines. It will also allow data to be entered on the host machine and then loaded into the Arduino and sent to the AS1.

This work is completed and working like a charm. It will be described in the next post.

Phase two will be to store these on an SD card in named files to allow easy record and playback of multiple data sets. None of this requires any new code on the AS1. Also note that the SD card is written using a standard FAT filesystem so the files can be edited on a PC if you so desire.

So far the coding work is Arduino based and the AS1 side involves me getting to grips with the AS1 monitor and hardware design.

Phase three will be to write a new high speed parallel storage interface for the AS1 to the Arduino making use of the second AS1 8154 IO chip - obviously the code will have to be designed so that other IO devices can easily be used with only a little recoding.

Of course this means you will need to have code running on the AS1 to handle the new interface.
I have no eprom programmer, so burning utilities to EPROM is not something I can do. It also assumes you have the space to put an eprom on your AS1 system. I suspect this is not an option for a lot of folk.

So I plan to make the finished product allow two modes of transfer. One will be the original cassette interface, and one will be the new parallel interface. Switch on your AS1, transfer the new (hopefully compact) code via the cassette interface, and then switch to the new mechanism.

Phase four will be to make use of a touchscreen interface on the Arduino to create a self contained unit used for data storage for the AS1 and remove the requirement for a desktop machine to talk to the Arduino.

So, apologies for the long ramble, but thats the idea, and as I say, the first phase is up and running and will be detailed in the next post.

I hope this will be of interest to some of you. Regardless, it should keep me busy over the next few weeks (months?).

Kazzie
Posts: 1600
Joined: Sun Oct 15, 2017 8:10 pm
Location: North Wales
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Kazzie » Mon Jun 15, 2020 12:17 pm

That's a really in-depth summary of your goals, which are definitely worth pursuing. =D>

I was wondering: Is your digital cassette recorder working through the existing cassette interface (i.e. dealing with sine waves) or wired directly to the INS8154's PA7 and PA6 (and using TTL logic levels)?
BBC Model B 32k issue 7, Sidewise ROM board with 16K RAM
Archimedes 420/1 upgraded to 4MB RAM, ZIDEFS with 512MB CF card
Acorn System 1 home-made replica

User avatar
anightin
Posts: 494
Joined: Thu Aug 23, 2018 2:03 pm
Location: Cambridge UK
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by anightin » Mon Jun 15, 2020 12:20 pm

Good luck in your project, sounds both fun and educational.

The cassette port is fiddly to set up a best, so any reliability improvements will be welcome.

Looking forward to the photos and happy to try and replicate on my System 1 as needed :D

Tel
Posts: 8
Joined: Sat Mar 16, 2019 3:49 pm
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Tel » Mon Jun 15, 2020 1:12 pm

In reply to Kazzie, i'm using the PA6/7 pins to make a direct link to the Arduino. It can handle 5v levels without issue, and the pins are easily accessible on the AS1 inter-board connector. I'll provide more detail in the post showing the actual interface in operation.

Tel
Posts: 8
Joined: Sat Mar 16, 2019 3:49 pm
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Tel » Mon Jun 15, 2020 7:00 pm

OK, phase one, I want to be able to transfer data between the AS1 and the Arduino. This is to be done via the existing cassette interface software procedures.

I'm only going to cover outgoing from AS1 to Arduino in this chunk as it gets long, and I don't want to bore folk. Incoming is the next bit.

First question, do I use the casette lines or go for a TTL line somewhere?

In my opinion going for the cassette multi-tone lines makes the job a lot more complicated. I'd have to interpret the tones, time slice them into bits, and then build the bits into bytes. Fiddlesome.

So I'm going for the TTL lines. Which ines and where?
Looking at the AS1 circuit diagram, and the monitor listing it seems sensible to go straight for PA6 and PA7. They are both available on the inter board edge connector. The output line can be tapped cleanly.
Cass IF Connector.jpg
However the input line would have a bit of a level conflict problem if I simply tapped a wire to it. So I need to remove IC4 (the CD4013). This IC is used to generate the feed into PA7. Removing the IC is an easy thing to do if you've socketed it. If not, then sorry, you have some track cutting to consider.
Obviously the audio tape interface won't work without IC4, but as I'm not planning on using it, I'm not worried.
No ic4.jpg
OK, I've got my three lines - data out, data in, and ground. Connect these to the Arduino.
AS1 plus Arduino.jpg
There are plenty of Arduino lines to choose from, but I've gone for Digital 8 for input and Digital 9 for output. Please ignore the other Arduino wires - I'm playing.

For AS1 to tape, configure Arduino digital port pin 8 as a digital input with internal pull-up. This avoids having to add a pull-up resistor to the AS1.

Next step, some code to handle the bit stream. Now this is where it gets fiddly. I read the monitor prom code for putbyte (and 'save') and determined what it did and how. I also see the inter bit delays used in the code to achieve 300 baud transfer rates.

Basically to send a byte, the CASOUT line (PA6) is normally high, and set low for one bit length (3.3ms) to represent a start bit for the next byte. The eight data bits are then sent for one bit length each. The line is then taken high again. This is repeated for each byte being sent out.

There is no sense checking as such in the monitor prom, so if things get confused, they get confused. I've done the same with my code. It's not bomb proof. Not worth the effort.

In order to send a data stream out using the 'S{ave}' option, the AS1 sends a four byte header and then the data itself. The header is the end address (high byte first), then the start address, (high byte) first.

So for a data set starting at 0x0200 consisting of four bytes 55 00 55 00, the stream would be: 02 04 02 00 55 00 55 00

And just to make life fiddly, the bits are transferred most significant to least significant, so you have to read a trace backwards.

You can see a similar stream in this pic.
Sample of start of save bitstream.png
I also looked at how the loadbyte code worked - to see that it looked for the drop from high to low, and then waited for the middle of the bit before sampling.

So, detect the drop (lets say this is time zero). Wait for a bit and a half (about 4.9ms) which is the start bit and half of the first bit of the byte and you should be nicely in the middle of bit 1. Sample the level. Wait 3.3ms sample bit2, wait 3.3ms, sample bit3 etc up to bit 8.

Repeat ad-infinitum until the data has all been received from the AS1 and printed.

And thats what my Arduino sketch (thats what they call an Arduino code segment) does. It is shown below. It collects 8 bits at a time, converts them into two hex characters, and prints them out. And then repeats.

Simple as that. But this gives you a hex data stream you can cut, paste, save, and so on.

It doesnt detect the end of transmission - it just stops printing as the CASOUT line stays high from this point in. I could try to do the math and use the first four bytes to identify when it's all been sent. But why bother. Just ask the human to press a key. Same as they would when they switch off the cassette player :-)
Arduino Console Output - AS1 out.jpg
For info, a sketch assumes a setup() procedure (where you configure things), and a loop()
procedure (where the work is done) will exist. Generally, loop() runs for ever.

No saving or loading to file yet - thats for later phases. This is a very manual process but it proves the
idea can be made to work.

Next post, the Arduino code that does the writing back to the AS1.

Any questions, or obvious screamers I've missed please let me know. Oh - and apologies - all my nice code line indenting has been removed - maybe someone can tell me how to maintain leading line spaces in a post?

=========================

Code: Select all

int cassInPin = 8;  // The digital pin to use for input.

void setup() 
{  
  Serial.begin(9600);  // Set up the serial interface to the PC.
  Serial.println("Starting...");  // Prints the phrase Starting... just so you know its running.
  pinMode(cassInPin, INPUT_PULLUP);   
}


void printHex(int num, int precision) // Print a value as a pair of hex digits.
{
  char tmp[16];
  char format[16];

  sprintf(format, "%%.%dX", precision);

  sprintf(tmp, format, num);
  Serial.print(tmp);
}

void loop() 
{ 
  if (digitalRead(cassInPin) == LOW)  // Wait for a low transition. Start bit.
  {
    int hexValue=0;
    delayMicroseconds(4900);  // Get to the middle of bit 1.
    
    for (int i = 0; i <= 7; i++) // Build a byte.
    {
      if (digitalRead(cassInPin) == LOW)
      {
        bitClear(hexValue, i);  // If we have a low, then set this bit to zero.
      }
      if (digitalRead(cassInPin) == HIGH)
      {
        bitSet(hexValue, i);  // If we have a high, then set this bit to one.
      }
      
      delayMicroseconds(3300);  // Wait for the next bit sample.
    } // for 0..7 - build a byte.
    
    printHex(hexValue, 2); // Print that byte.
  } // if found low
}

Kazzie
Posts: 1600
Joined: Sun Oct 15, 2017 8:10 pm
Location: North Wales
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Kazzie » Mon Jun 15, 2020 8:07 pm

An interesting read: I've just glanced over it for now, I'll have a proper read when I'm using something larger than a phone screen.

I'd guessed you'd be using the Phi2 signal as well cor synchronisation purposes, but apparently not.
Tel wrote:
Mon Jun 15, 2020 7:00 pm
Any questions, or obvious screamers I've missed please let me know. Oh - and apologies - all my nice code line indenting has been removed - maybe someone can tell me how to maintain leading line spaces in a post?
Try using [ code ] tags (or use the </> icon above the edit box). :)
BBC Model B 32k issue 7, Sidewise ROM board with 16K RAM
Archimedes 420/1 upgraded to 4MB RAM, ZIDEFS with 512MB CF card
Acorn System 1 home-made replica

User avatar
richardtoohey
Posts: 3900
Joined: Thu Dec 29, 2011 5:13 am
Location: Tauranga, New Zealand
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by richardtoohey » Mon Jun 15, 2020 11:22 pm

An interesting read =D>

I've put the code tags on the code in your last post - hope that helps & is OK with you.

Tel
Posts: 8
Joined: Sat Mar 16, 2019 3:49 pm
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Tel » Wed Jun 17, 2020 7:31 pm

Apologies for the delay in posting the final part of this chunk. The data load back into the AS1,
but sunshine means gardening and lots of dog walking!

OK - not a lot to say about this bit, its the flipside of the coin with sending data back into the AS1 from the Arduino. The characters are sent least significant bit first preceded by a start bit of a level one to zero change. I messed up in the earlier post saying things were sent most sig bit first - they aren't - its least sig bit first, moving thru to bit eight. My bad! I blame the ageing process :-)

Just to answer Kazzies query about using a clock signal to sync things - the AS1 cassette interface is so simplistic it has no syncing other than the start bit level one to zero drop. Then relies on a bit length delay to the next bit, for a count of 8 bits. then back to waiting for a start bit level change.

So unless theres a major issue, theres little likelyhood of things falling out of sync.

The code shown below takes the string as received from the cassette out program from the earlier post - a string of hex bytes. The first four are the end and start addresses. Then the data stream itself.

Each byte is split into two single characters. Each character is decoded into a four bit pattern. And finally each bit is sent out on the Arduino output pin and left there for a bit length (3.3ms) before the next bit is sent.

At the end of the byte, a level one is sent to signify end of character, and in readiness for the next byte (if there is one).

The code is far from perfect in terms of elegance - I'll clean it up later on - this is just to prove the idea.

Once I learn how to get a decent size video file (ie not 20Mb+!!) I'll upload some examples. In the meantime you'll have to take my word for it that there transfers work fine - better still, get hold of an Arduino and try it yourself.

The code has an obvious set of examples: blanks, duck shoot and hex convert programs from the manual. The variable putbyteString holds the hex sequence - you can put in here anything you want as long as the first four bytes are the end address and the start address - the examples all go from 0200 to 023F or 023A below. I haven't built in any case correction yet - so please use upper case, or adjust the code.

As usual, any questions, or obvious bloopers, please let me know. Hopefully; if time and weather allows - i.e. if it rains!; I'll have the SD card file load and save up and running in the next few days or so, which should give a real substitute for the cassette interface for anyone wanting to try it.

Oh, and here's the Arduino sketch:

Code: Select all

int cassInPin = 8;
int cassOutPin = 9;

// String to be sent.

// Duck Shoot
char putbyteString[]="023F0200A91F850EA900A20786209510CA10FBA900A6209510A961CA1002A20795108620A20E200CFEC520F005CAD0F6F0E1A91CA6209510A9FF850E200CFE90C34C04FF";

//Hex to Dec
//char putbyteString[]="023A020084208421A2202088FEF8A2008622A520D006A521F013C621C62018986901A88A6900AA90E9E622B0E584208621A2202064FE88A522207AFE4C04FF";

//Blanks
//char putbyteString[]="023A020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";

char h0[5]="0000";
char h1[5]="0001";
char h2[5]="0010";
char h3[5]="0011";
char h4[5]="0100";
char h5[5]="0101";
char h6[5]="0110";
char h7[5]="0111";
char h8[5]="1000";
char h9[5]="1001";
char ha[5]="1010";
char hb[5]="1011";
char hc[5]="1100";
char hd[5]="1101";
char he[5]="1110";
char hf[5]="1111";

void sendZero()
{
  //Serial.print("0");
  digitalWrite(cassOutPin, LOW);
  digitalWrite(LED_BUILTIN, LOW);
  delayMicroseconds(3300);
}

void sendOne()
{
  //Serial.print("1");
  digitalWrite(cassOutPin, HIGH);
  digitalWrite(LED_BUILTIN, HIGH);
  delayMicroseconds(3300);
}

void sendStartBit()
{
  sendZero();
}

void sendStream( char bitStream[5] )
{
  for (int i=3; i>=0; i--)
  {
    if ( bitStream[i]=='1' )
    {
      sendOne();
    }
    else
    {
      sendZero();
    }
  }
}

void nibbleToBin( char nibbleIn )
{
  if (nibbleIn == '0' ) 
  {
    sendStream( h0 );
  }
  else if (nibbleIn == '1' ) 
  {
    sendStream( h1 );
  }
  else if (nibbleIn == '2' ) 
  {
    sendStream( h2 );
  }  
  else if (nibbleIn == '3' ) 
  {
    sendStream( h3 );
  }  
  else if (nibbleIn == '4' ) 
  {
    sendStream( h4 );
  }  
  else if (nibbleIn == '5' ) 
  {
    sendStream( h5 );
  }  
  else if (nibbleIn == '6' ) 
  {
    sendStream( h6 );
  }  
  else if (nibbleIn == '7' ) 
  {
    sendStream( h7 );
  }  
  else if (nibbleIn == '8' ) 
  {
    sendStream( h8 );
  }  
  else if (nibbleIn == '9' ) 
  {
    sendStream( h9 );
  }  
  else if (nibbleIn == 'A' ) 
  {
    sendStream( ha );
  }  
  else if (nibbleIn == 'B' ) 
  {
    sendStream( hb );
  }  
  else if (nibbleIn == 'C' ) 
  {
    sendStream( hc );
  }
  else if (nibbleIn == 'D' ) 
  {
    sendStream( hd );
  }
  else if (nibbleIn == 'E' ) 
  {
    sendStream( he );
  }
  else if (nibbleIn == 'F' ) 
  {
    sendStream( hf );
  } 
  else 
  {
    Serial.print("Invalid Nibble Received: ");
    Serial.println( nibbleIn );
  }
}

// Take two characters and pass out in binary form.
// Valin2 is low nibble, valin1 is high nibble.
// Bits go out low bit first, so if get 0x8 and 0x3
// We send: 1100 0001, dont forget the start bit.
char byteToBin( char valin1, char valin2 )
{
  sendStartBit(); // Send start bit (high to low transition).
  nibbleToBin( valin2 ); // Send low nibble first.
  nibbleToBin( valin1 ); // Send high nibble second.
  sendOne();  // Set line high ready for the next byte.  
  //Serial.println();  
}

void loop() 
{ 
  delay(10000000);
}

void setup() 
{  
  Serial.begin(9600);
  Serial.println("Starting...");
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(cassInPin, INPUT_PULLUP); 
  pinMode(cassOutPin, OUTPUT);
  digitalWrite(cassOutPin, HIGH);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(5000); // Let the output be stable for 5 seconds before transmitting.

  int strLen=sizeof(putbyteString);
  Serial.println("Starting Transmission");
  for (int i=0; i<= strLen-2; i+=2)
  {
    // Break the string into two character chunks (a byte).
    // Pass to hexval as two individual hex (4 bit) characters and passed out.
    Serial.print(" ");
    Serial.print(putbyteString[i]);
    Serial.print(putbyteString[i+1]);
    byteToBin( putbyteString[i], putbyteString[i+1] );
  }
  sendOne();
  Serial.println("Ending Transmission");
}

Tel
Posts: 8
Joined: Sat Mar 16, 2019 3:49 pm
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Tel » Mon Jun 29, 2020 12:33 pm

Short update. The SD card code is up and running.

I can now save AS1 code via the tape interface routines onto an SD card in individual files (for example: duckshoot), and then reload the files at will by selecting the named file and running the tape load on the AS1.

Full details and code listing later when I have tidied the code up and sorted out pictures of the Arduino and SD card interface.

Good weather slowed play a great deal.

Tel
Posts: 8
Joined: Sat Mar 16, 2019 3:49 pm
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by Tel » Tue Jun 30, 2020 10:12 am

OK, here's a working pre-lim code cut and basic connection info for the Arduino SD cassette storage project. The code could be improved and optimised considerably.

The picture shows the Arduino and the Micro SD card interface. The SD card interface is connected as per its documentation (see below), with CS connected in my instance to digital pin 10.
Arduino SD Card.png
The SD card pins (apart from CS) are connected to their matching pins on the Arduino (connection locations vary by board type).
Ard plus sd.jpg
The cassette interface is detailed earleir in this thread. Three wires, Ground, cassIn and cassOut from the AS1.

The SD card needs to be pre-formatted in FAT32 format.The Arduino interaction is via the builtin console.

The menu operation is basic. Three options, list card contents (D), load file into AS1 (L), or save file from AS1 (S).

When saving, set up the AS1 ready to save, select option S, and enter a filename to save to. The code currently overwrites the file if it already exists. I will look at making this an 'Are you sure?' style question later.

Enter the filename, press enter, and the Arduino will wait for data to be sent to it. Press an AS1 command key to begin saving. The Arduino will display the data as it is received. Press return on the Arduino when the transmission has ended and the data will be saved to the filename you provided.

As you can see, the code currently assumes that when saving a file to SD card you will press return when the transfer has been completed (a bit like switching off your cassette recorder). This could be improved to determine the sequence length based upon the first two words of the stream. But it's not that much of an inconvenience, and works fine just as it is.

Directories on the SD card etc are supported, but I have not done anything about these yet.

When loading a file, enter the filename to be sent to the AS1, press enter. Then (within five seconds) press 'l'oad on the AS1. Off you go.

Simple as that.

It's far from bullet proof, I will add the extra code when I have a chance, but the basics work and work well IMHO. Obviously feel free to tweak and adjust as you see fit for your system.

I'm going to veer off the plan now as I want to look at a better way of manually entering data into the AS1.

I'm making use of 'xa' to assemble code on my desktop, which can then be placed into files on the SD card and transferred via the cassette interface. However I want a serial keyboard style interface into the AS1, so next stop is a UART of some sort, and some code to drive it and interpret the commands. Watch this space.

Code: Select all


#include <SPI.h>
#include <SD.h>

// SD Control pin.
int SDctl = 10;

// Two digital pins used for cassette in and out.
int cassInPin = 8;  // Data from AS1
int cassOutPin = 9;  // Data to AS1

// Using cassette interface or new HS interface.
int txMode=0; // 0=Cassette I/F  1=High Speed I/F

// myFile is the currently open file on SD card.
File myFile;
const byte stringSize=100;
char inputString[stringSize];  // Used for filename.

// serialData is used for the data sent and received from AS1
const int serialSize=1000; // Using 1k for the moment.
char serialData[serialSize];
int dataLoop=0; // Loop counter places char in string.

// h0->hf are bit patterns for hex 0->F.
char h0[5]="0000";
char h1[5]="0001";
char h2[5]="0010";
char h3[5]="0011";
char h4[5]="0100";
char h5[5]="0101";
char h6[5]="0110";
char h7[5]="0111";
char h8[5]="1000";
char h9[5]="1001";
char ha[5]="1010";
char hb[5]="1011";
char hc[5]="1100";
char hd[5]="1101";
char he[5]="1110";
char hf[5]="1111";


// Ensure all setup completed.
void setup() 
{
  // Serial port for comms to board. 
  Serial.begin(9600);
  Serial.println("Starting...");

  pinMode(LED_BUILTIN, OUTPUT);  // LED to match signal line.
  pinMode(cassInPin, INPUT_PULLUP);  // Input line with pullup as not one on AS1
  pinMode(cassOutPin, OUTPUT);  // Output to AS1
  
  digitalWrite(cassOutPin, HIGH);  // Make sure output to AS1 is high by default.
  digitalWrite(LED_BUILTIN, HIGH);  // LED matches it.

  // Initialise SD card interface.
  Serial.print("Initializing SD card...");
  if (!SD.begin(SDctl)) 
  {
    Serial.println("SD card initialization failed!");
    while (1);
  }
  Serial.println("SD card initialization done.");

  // Display the main menu.
  MainMenu();
}


// Blocking get character.
char GetChar()
{
  boolean newData;
  char receivedChar;

  delay(50);
  receivedChar='\0';
  newData = false;
  while ( newData == false )
  { 
      if (Serial.available() >0) 
      {
        receivedChar = Serial.read();
        newData = true;
      }
  }
  return toupper(receivedChar);  // Force upper case.
}


char *GetString() // Blocking. Populates global: inputString
{
  static byte ndx; // never count more than 255.
  char endMarker = '\r'; // End with CR.
  char inch;
  boolean newData = false;

  ndx=0;
  inputString[0]='\0';
  while ( newData == false ) 
  {
    inch = GetChar();
    //Serial.print(inch);  // Possible character echo.
    if (inch != endMarker) // If valid character, save it and increment index.
    {
      inputString[ndx] = inch;
      ndx++;
      if (ndx >= stringSize)  // If we overrun buffer size, keep overwriting last character.
      {
        ndx = stringSize - 1;
      }
    }
    else // inch is end marker.
    {
      if (ndx==0) continue; // if the string is empty loop again.
      inputString[ndx] = '\0'; // terminate the string
      newData = true; // Set flag to exit while loop.
    }
  }
}


void LoadFile() // Open file and send to AS1.
{  
  Serial.println("Loading file into Acorn System 1");
  Serial.println();
  Serial.print("Enter Filename: ");
  GetString();
  Serial.print("Opening file:");
  Serial.println(inputString);
  Serial.println();
  
  myFile = SD.open(inputString); // Try to open file.
  if (myFile)
  {
    // read from the file until there's nothing else in it:
    while (myFile.available()) 
    {
      int len=myFile.read(serialData,serialSize);
      serialData[len]=0;
      sendDataToAs1();
    }
    // close the file:
    myFile.close();
  }
  else 
  {
    // if the file didn't open, print an error:
    Serial.print("Error opening file::");
    Serial.print(inputString);
    Serial.println("::");
  }
}


void SaveFile()
{
  String localFileName = "";
  
  Serial.println("Saving file from Acorn System 1");
  Serial.println();
  Serial.print("Enter Filename: ");
  GetString();
  Serial.println(inputString);
  // Check if file already exists.
  // If so, prompt for overwrite.
  // If Overwrite, remove and open clean.
  SD.remove(inputString);
  // Open file for writing.
  Serial.print("Opening file:");
  myFile = SD.open(inputString, FILE_WRITE);
  if (myFile)
  {
    SaveDataToFile(myFile); // Save data to file.
    myFile.close(); // Close file.
  }
  else 
  {
    // if the file didn't open, print an error:
    Serial.print("Error opening file::");
    Serial.print(inputString);
    Serial.println("::");
  }
}


void ToggleMode()  // Switch between high speed i/f and cassette i/f.
{
  if ( txMode == 0 )
  {
    txMode=1;  // New I/F
  }
  else
  {
    txMode=0;  // Cassette I/F
  }
}


void PrintDirectory(File dir, int numTabs) 
{
  while (true) 
  {
    File entry =  dir.openNextFile();
    if (! entry) 
    {
      // no more files
      break;  // Exit while true.
    }
    for (uint8_t i = 0; i < numTabs; i++) 
    {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    
    if (entry.isDirectory()) 
    {
      Serial.println("/");
      PrintDirectory(entry, numTabs + 1);
    }
    else 
    {
      // files have sizes, directories do not
      Serial.print("\t\t");
      Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}


void ListFiles()
{
  Serial.println("Files on SD card are: ");
  Serial.println();
  File root = SD.open("/");
  PrintDirectory(root, 0);  // PrintDirectory tree walks (recursive).
  Serial.println();
}


void MainMenu()
{
  Serial.println("");
  Serial.println("");
  Serial.println("*******************************");
  Serial.println("*                             *");
  Serial.println("*   Acorn System 1 Storage    *");
  Serial.println("*                             *");
  Serial.println("*******************************");
  Serial.println("");
  
  if ( txMode == 0 )
  {
    Serial.println("Using Cassette interface");
  }
  else
  {
    Serial.println("Using High Speed interface");
  }
  Serial.println("");
  Serial.println("Select one of the following options:");
  Serial.println("S: Save File");
  Serial.println("L: Load File");
  Serial.println("");
  Serial.println("D: List SD Card Directory Contents");  
  //Serial.println("T: Toggle between Cassette / High Speed Interface");
  Serial.println("");
}

  
void Menu() 
{
  char format[50];
  
  for (;;) 
  {
    switch ( GetChar() ) 
    {
      case 'S': SaveFile(); MainMenu(); break;
      case 'L': LoadFile(); MainMenu(); break;
      case 'D': ListFiles(); MainMenu(); break;
      // case 'T': ToggleMode(); MainMenu(); break;
      default: continue;  // includes the case 'no input'
    }
  }
}


void printHex(int num, int precision) 
{
  char tmp[16];
  char format[128];

  sprintf(format, "%%.%dX", precision);

  sprintf(tmp, format, num);
  Serial.print(tmp);
 // myFile.println(tmp); // vvvvvv Out to file vvvvvvv
 // Write to file at this point busts the timing - function too slow !!!
 // So save to string for later write.
 serialData[dataLoop]=tmp[0];
 serialData[dataLoop+1]=tmp[1];
}


void SaveDataToFile(File fPtr) 
{ 
  dataLoop=0;
  
  while (Serial.available() <1)  // Wait for someone to press a key to end.
  {    
    if (digitalRead(cassInPin) == LOW)
    {
      //Serial.print("NewDump");
      int hexValue=0;
      delayMicroseconds(4900);
      for (int i = 0; i <= 7; i++) 
      {
        if (digitalRead(cassInPin) == LOW)
        {
          bitClear(hexValue, i);
        } // print 0
      
        if (digitalRead(cassInPin) == HIGH)
        {
          bitSet(hexValue, i);
        } // print 1
      
        delayMicroseconds(3300);
      } // for 0..7
    
      printHex(hexValue, 2);
      dataLoop=dataLoop+2;
    } // found low
 } // Someones pressed a key, end recording.
 
 Serial.read();  // get rid of the pressed key.
 Serial.println();
 serialData[dataLoop]=0;  // close the string with null.
 Serial.println(serialData);  // print the string
 myFile.println(serialData); // Save string to file.
}

void sendZero()  // put a zero on the cassette out line intio AS1.
{
  //Serial.print("0");
  digitalWrite(cassOutPin, LOW);
  digitalWrite(LED_BUILTIN, LOW);
  delayMicroseconds(3300); // Delay for a single bit length at 9600 bd.
}

void sendOne()  // put a one on the cassette out line intio AS1.
{
  //Serial.print("1");
  digitalWrite(cassOutPin, HIGH);
  digitalWrite(LED_BUILTIN, HIGH);
  delayMicroseconds(3300); // Delay for a single bit length at 9600 bd.
}

void sendStartBit()  // a start bit is a drop to zero.
{
  sendZero();
}

void sendStream( char bitStream[5] )  // for a four bit value, set line high or low for each bit in turn.
{
  for (int i=3; i>=0; i--)
  {
    if ( bitStream[i]=='1' )
    {
      sendOne();
    }
    else
    {
      sendZero();
    }
  }
}


// function looks at individual character nibble (e.g. in 9F, 9 and F are individual nibbles).
// it looks upo and sends the bit pattern for that nibble. 
void nibbleToBin( char nibbleIn )
{
  if (nibbleIn == '0' ) 
  {
    sendStream( h0 );
  }
  else if (nibbleIn == '1' ) 
  {
    sendStream( h1 );
  }
  else if (nibbleIn == '2' ) 
  {
    sendStream( h2 );
  }  
  else if (nibbleIn == '3' ) 
  {
    sendStream( h3 );
  }  
  else if (nibbleIn == '4' ) 
  {
    sendStream( h4 );
  }  
  else if (nibbleIn == '5' ) 
  {
    sendStream( h5 );
  }  
  else if (nibbleIn == '6' ) 
  {
    sendStream( h6 );
  }  
  else if (nibbleIn == '7' ) 
  {
    sendStream( h7 );
  }  
  else if (nibbleIn == '8' ) 
  {
    sendStream( h8 );
  }  
  else if (nibbleIn == '9' ) 
  {
    sendStream( h9 );
  }  
  else if (nibbleIn == 'A' ) 
  {
    sendStream( ha );
  }  
  else if (nibbleIn == 'B' ) 
  {
    sendStream( hb );
  }  
  else if (nibbleIn == 'C' ) 
  {
    sendStream( hc );
  }
  else if (nibbleIn == 'D' ) 
  {
    sendStream( hd );
  }
  else if (nibbleIn == 'E' ) 
  {
    sendStream( he );
  }
  else if (nibbleIn == 'F' ) 
  {
    sendStream( hf );
  } 
  else 
  {
    Serial.print("Invalid Nibble Received: ");
    Serial.println( nibbleIn );
  }
}


// Split a byte into two nibbles, translage to binary, and send out.
// Take two characters and pass out in binary form.
// Valin2 is low nibble, valin1 is high nibble.
// Bits go out low bit first, so if get 0x8 and 0x3
// We send: 1100 0001, dont forget the start bit.
char byteToBin( char valin1, char valin2 )
{
  sendStartBit(); // Send start bit (high to low transition).
  nibbleToBin( valin2 ); // Send low nibble first.
  nibbleToBin( valin1 ); // Send high nibble second.
  sendOne();  // Set line high ready for the next byte.  
  //Serial.println();  
}

void sendDataToAs1() 
{  
  // Sned byte stream to AS1 cassette interface.
  delay(5000); // Let the output be stable for 5 seconds before transmitting.

  int stringLen=strlen(serialData)-1;
  Serial.println("Starting Transmission.");
  for (int i=0; i<= stringLen-2; i+=2)
  {
    // Break the string into two character chunks (a byte).
    // Pass to hexval as two individual hex (4 bit) characters and passed out.
    // print them for visual confirmation.
    Serial.print(serialData[i]);
    Serial.print(serialData[i+1]);

    // now send to interface.
    byteToBin( serialData[i], serialData[i+1] );
  }
  
  sendOne();  // make sure the output line sits high by default.
  Serial.println();
  Serial.println("Ending Transmission.");
}


// Main loop - display menu and act on choices.
void loop()
{
  Menu();
}


User avatar
anightin
Posts: 494
Joined: Thu Aug 23, 2018 2:03 pm
Location: Cambridge UK
Contact:

Re: Arduino Based SD Card Storage for the Acorn System 1

Post by anightin » Tue Jun 30, 2020 2:01 pm

Hi Tel,

I may well be giving this a try thanks -- just waiting for an Arduino and SD adapter to arrive in the post :)

Thanks for your hard efforts. =D>

I do actually already have a spare ESP8266 that I used for a previous home automation project.

For my setup, I compile my System 1 assembler on my Raspberry PI (headless so I can run it from my laptop via RCP), then plug an audio jack from the laptop for playing back the resultant WAV file into the System 1.

I've got MQTT set up on the PI so one thought for a future project would be to do away with the WAV file and audio lead approach, and have the PI transmit the 6502 binary file to the ESP8266 over WiFi ready for the System 1 to load.

The possibilities are seemingly endless. :D

Andy

Post Reply

Return to “acorn atom and acorn system series”