💾 Archived View for consensus.circumlunar.space › arduino › serial_disk.txt captured on 2024-03-21 at 14:49:31.

View Raw

More Information

⬅️ Previous capture (2020-09-24)

-=-=-=-=-=-=-

# Serial Disk

My first arduino project. I'm probably more excited about this than I 
ought to be. First, some impressions:

1. Arduino is way easier than is legally allowed. You can build stuff
   that would have taken months, in minutes.
2. The hardware is dirt cheap. I don't know how reliable it is, but
   boy howdy, this stuff is affordable. It's also easy to get. It's
   almost too good to be true.
3. Whoever is designing Arduino hardware must know what hobbyists
   want and need. They're doing it right, I feel, in terms of having
   a product that people actually want.

## The Project

On to the actual project. This is related to my z80 projects- 
specifically, to my prototype board z80 computer with ROM BASIC based on 
Grant Searle's "Simple z80" design. In that project, the computer has 
rom basic and a serial port, and nothing else. You boot it up, write 
your code, and play, then start over when you power on the next day.

The system was fun, but I wanted storage so desperately. I built a new 
clock circum and a cassette interface, and cobbled together the ability 
to LOAD/SAVE from cassette at 300 baud. It was a beautiful thing, and it 
almost worked. I say almost; it worked, but it wasn't super reliable, 
and it was so slow. I could live with the slow (there's a certain charm 
to it!) but the reliability was a killer. I don't want to deal with 
corrupt recordings.

The solution, in my mind, was this: build a small box that you can 
connect to the z80's serial port, which upon pressing a button will type 
"LIST<enter>" and record the output to a file on an SD card, then 
display a list of files and let you select one and press "LOAD" to send 
it to the serial port. Basically, a serial port disk device, for 
storage. Then, I could use my z80 computer somewhat independantly 
(meaning, without my laptop powered on), *and* have mass storage. 
Ideally, the device would run at 115200, the native speed of the z80 
build.

## The Build

At first, I thought I might be able to figure some solution out from 
scratch. I'm quite certain this is possible. Unfortunately, time and 
knowledge were not on my side toward this end. I settled on the idea of 
Arduino, something I'd always wanted to try anyway.

My first testing was with an Uno that I had laying around from a thrift 
store find. It had everything I needed to fiddle. I got an i2c 16x2 LCD 
working, and that was incredibly simple and fast. I ordered an SD 
adapter; that worked too, with very little hassle. Next I tried out a 
few different types of buttons, and settled on a 1-pin analog solution, 
which I soldered up with a few buttons and resistors. 

Then I hit a small roadblock: the Uno has only one serial connection, 
and it's shared with the programmer (as I understand it). I ordered a 
Mega 2560 for a couple bucks on ebay, changed the code a tiny bit, and 
I'm off again. Connected up a TTL-RS232 that I had around, and now the 
thing has a DB9 serial port of the correct gender for my project.

With a tiny bit of coding (I've not done C++ for a long time!), the 
thing works as expected, which is:

- You power the unit, the LCD displays version info and init info
- After successful init, the LCD lets the user know the unit is ready
- You may press < or > to navigate the files on the SD card
- You may press "LOAD" to send the selected file from SD to Serial at 
  115.2k (with a tx character delay of 6ms, which my board needs)
- You may press "SAVE" to send "list<enter>" over the serial and then
  recieve characters and save them to a new incremented file.

The result is mass storage usable with the z80 computer and without 
another computer in the mix (as long as you don't count the arduino. 
It's cheating compared to the cassette interface, but I don't care. I'm 
in this to have fun!)

All that remains, as far as the build goes, is to get it into a suitable 
enclosure.

## The Code

DISCLAIMER: There is nothing impressive below. Like I said, I've not 
done C++ in a while, and even when I did it wasn't that great. I'm not a 
programmer, just a hobbyist. If you have tips about the code, please 
send them my way!

The code Works(TM). It's simple, and it uses the pre-built libraries for 
the bits of hardware I'm using. I believe this is the Arduino Way(TM). 
I've included comments, to hopefully make things a little more readable. 
This runs on Mega 2560, but it runs with very few changes on Uno as well 
(though, I'm not entirely sure about the Serial conflicts on Uno). 

/*
 * Serial Disk v0.6
 * 
 * A serial disk program for z80 (or other) serial console computers.
 * Useful for saving/loading ROM BASIC programs, file transfers, etc.
 * Public domain, tfurrows@sdf.org, 10/21/2019

  On Arduino Mega 2560
  ====================
  SD to SPI:
   CS   - pin 4
   MOSI - pin 51
   MISO - pin 50
   SCK  - pin 52

  I2C 16x2 LCD:
   SDA  - pin 20
   SCL  - pin 21

  Analog Buttons 1-5 to A1
  (using 5 2k resistors; adjust values as needed)

  TTL->232 4-pin:
   RX   - pin 14
   RX   - pin 15



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

LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

String *fileList, *fileSizes;
int fileCount, i, buttonState = 0;
static unsigned long serialTimer;

void setup() {
  // Init button input pin
  pinMode(A1, INPUT_PULLUP);

  // Init I2C LCD
  lcd.begin(16, 2);
  lcd.backlight();

  // Welcome the user
  lcd.clear();
  lcd.print("Serial Disk v0.6");
  lcd.setCursor(0, 1);
  lcd.print("Initializing ...");
  delay(1000);

  // Init SD
  if (!SD.begin(4)) {
    // SD failed, notify
    lcd.setCursor(0, 1);
    lcd.print("SD card error   ");
    while (1);
  }
  // Get directory contents
  getDirListing("/");

  // Init Serial
  Serial3.begin(115200);

  // Done with init
  lcd.setCursor(0, 1);
  lcd.print("Ready.          ");
}

void loop() {
  buttonState = readButtons(1);

  if (buttonState > 0) {
    // Buttons: 1=prev, 2=next, 3=load, 4=save, 5=unassigned

    if (buttonState == 1) {
      // BUTTON 1: < Previous File
      delay(200);
      i--;
      if (i < 0) i = fileCount - 1;
    }

    if (buttonState == 2) {
      // BUTTON 2: > Next file
      delay(200);
      i++;
      if (i >= fileCount) i = 0;
    }

    if (buttonState == 3) {
      // BUTTON 3: LOAD, transfer from SD to serial
      File loadFile = SD.open(fileList[i]);

      if (loadFile) {
        lcd.clear();
        lcd.print("LOADING...");

        while (loadFile.available()) {
          Serial3.write(loadFile.read());
          Serial3.flush();
          delay(6); // tx char delay, needful for my setup
        }

        loadFile.close();
        lcd.setCursor(0, 1);
        lcd.print("Done.");
      } else {
        lcd.clear();
        lcd.print("File LOAD Error.");
      }
      delay(2000); // allow time to view status msg
    }

    if (buttonState == 4) {
      // BUTTON 4: SAVE, recieve from serial, save to SD

      lcd.clear();
      lcd.print("SAVING...");

      File saveAs;
      char saveFile[12];

      // Increment filename based on number of files on SD
      sprintf(saveFile, "SAVE%03d.TXT", fileCount + 1);
      saveAs = SD.open(saveFile, FILE_WRITE);

      // This is for saving ROM BASIC; send "LIST" command
      // to the BASIC interpreter (basic programs could be
      // written to accomodate this in their save routines).
      Serial3.write("\r\n");
      delay(300);
      Serial3.write("list\r\n");

      // Give 2s timeout to wait for data
      serialTimer = millis();
      while (millis() - serialTimer < 2000) {
        while (Serial3.available()) {
          saveAs.write(Serial3.read());
          serialTimer = millis();
        }
      }

      Serial3.write("\r\n");
      Serial3.flush();

      saveAs.close();
      lcd.setCursor(0, 1);
      lcd.print("Done.");

      delay(2000); //  allow time to view status msg
      getDirListing("/");
      i = 0;
    }

    // Update screen with currently selected file info
    lcd.clear();
    lcd.print("File ");
    lcd.print(i + 1);
    lcd.print(" of ");
    lcd.print(fileCount);

    lcd.setCursor(0, 1);
    lcd.print(fileList[i]);
    lcd.print(" ");
    lcd.print(fileSizes[i]);
    lcd.print("k");
  }

}

int readButtons(int pin) {
  // Uses a common 1-pin analog button setup.
  // change based on your input/button hardware

  int btn, vin = 0;
  vin = analogRead(pin);

  /*
     Use to get your values, based on the
     resistors you used, etc.
    if (vin<1000) {
    lcd.clear();
    lcd.print(vin);
    }
  */

  if (vin > 1000) btn = 0;
  if (vin > 222 && vin < 248) btn = 1;
  if (vin > 187 && vin < 203) btn = 2;
  if (vin > 152 && vin < 166) btn = 3;
  if (vin > 115 && vin < 125) btn = 4;
  if (vin < 77) btn = 5;

  return btn;
}

void getDirListing(String root) {
  // Save an updated listing of the files in a directory on the SD card

  if (fileList) delete [] fileList;
  if (fileSizes) delete [] fileSizes;
  fileCount = 0;

  File dir = SD.open(root);

  while (true) {
    File entry = dir.openNextFile();
    if (!entry) {
      dir.rewindDirectory();
      break;
    } else {
      fileCount++;
    }
    entry.close();
  }

  fileList = new String[fileCount];
  fileSizes = new String[fileCount];

  int curFile = 0;

  while (true) {
    File entry =  dir.openNextFile();
    if (! entry) break;

    if (! entry.isDirectory()) {
      fileList[curFile] = entry.name();
      fileSizes[curFile] = entry.size() / 1024;
      if (entry.size() / 1024 < 1) fileSizes[curFile] = "<1";
      curFile++;
    }
    entry.close();
  }
  
  dir.close();
}