Compare commits

...

2 Commits

Author SHA1 Message Date
nino-porcino c83a357ab6 Update README.md 2022-03-17 12:42:42 +01:00
nino-porcino 8f345b801e tagged file names (LOAD, RUN, SAVE, DIR) 2022-03-17 12:42:35 +01:00
8 changed files with 481 additions and 53 deletions

View File

@ -1,29 +1,105 @@
# SD CARD COMMANDS
# SD CARD
## I/O
READ,WRITE for pure binaries,
LOAD,BSAVE,RUN for basic/prodos format and .PRG
TYPE
DUMP
- all numbers must be provided in hexadecimal format unless specified
- arguments in `[]` brackets means they are optional
- nested paths are allowed with the `/` character, e.g. `/`, `/foder1/foo`
- no path given implies current working directory
## FILE
DEL
REN
COPY
## TAGGED FILE NAMES
## DIR
DIR
LS
CD
MKDIR
RMDIR
File names containing a tag `#` character have a special meaning: the part after the `#`
indicates the file type (two characters) and the hex loading address (4 characters).
## MISC
HELP
JMP
BAS
STAT
MOUNT
TIME
EXIT
`#06` for plain binary files
`#F1` for BASIC programs
E.g.:
`BASIC#06E000` is binary file named `BASIC` that loads at address `$E000`.
`STARTREK#F10300` is a BASIC program named `STARTREK` that loads at address `$0300`.
Tagged file names are used by the `LOAD`, `RUN`, `SAVE` and `DIR` commands to simplify working with files. For example to execute the above files, it's enough to type:
```
LOAD BASIC
RUN STARTREK
```
## COMMANDS
`READ filename startaddress`
Reads a binary file from the SD card and loads it in memory at the specified address.
`WRITE filename startaddress endaddress`
Writes the memory range from `startaddress` to `endaddress` (inclusive) in a file on the SD card.
`TYPE filename`
Reads the specified ASCII file from the SD card and prints it on the screen. Press any key to stop the printing and return to the command prompt.
`DUMP filename [start] [end]`
Reads the specified binary file from the SD card and prints it on the screen in hexadecimal format. `start` and `end` are optional and are used to print a smaller portion of the file. Press any key to stop the printing and return to the command prompt.
`LOAD filename`
Loads a file from the SD card. `filename` refers to a "tagged file name" described above. For convenience, `filename` can be partially given, the first matching file will be loaded.
`SAVE filename start end`
Saves a file to the SD card. If `start` and `end` are specified, a binary file with tag `#06` will be created with the memory content from the address range `start`-`end` (included).
If `start` and `end` are not specified, the BASIC program currently loaded in memory will be created with the corresponding `#F1` tag.
`RUN filename`
Same as `LOAD` but runs the file after loading it. Binary files are exectuted at the starting address specified in the file name tag; BASIC files are `RUN` from the BASIC interpreter.
`DEL filename`
`RM filename`
Deletes a file from the SD card.
`DIR [path]`
`LS [path]`
Lists the files from the specified directory, or from the current directory if no path is given. `LS` has a shorter but quicker output format. Press any key to stop the file listing and return to the command prompt.
`CD path`
Changes the current working directory to the specified path. The current directory is also shown in the command prompt.
`MD path`
`MKDIR path`
Creates the specified directory.
`RD path`
`RMDIR path`
Removes the specified directory. The directory to remove must be empty (no files or directories within).
`PWD`
Prints on the screen the current working directory.
`BAS`
Prints `LOMEM` and `HIMEM` pointers from the BASIC program currently loaded in memory.
`JMP address`
Makes the CPU jump at the specified address.
`TIME value`
Set the internal timeout value used in the I/O operations with the SD cards.
`TEST`
Internal test.
`EXIT`
Exits to the WOZ monitor

View File

@ -36,7 +36,7 @@ in Verilog syntax: data = { PORTB[1:0], PORTD[7:2] };
*/
#define FASTWRITE 1
// #define FASTWRITE 1
#ifdef FASTWRITE
#define get_cpu_strobe ((PORTC >> 1) & 1)
@ -47,7 +47,8 @@ in Verilog syntax: data = { PORTB[1:0], PORTD[7:2] };
#endif
#include <Regexp.h>
// #include <Regexp.h>
#include <SPI.h>
#include "SdFat.h"
@ -210,6 +211,7 @@ void send_byte_to_cpu(int data) {
const int CMD_READ = 0;
const int CMD_WRITE = 1;
const int CMD_DIR = 2;
const int CMD_LOAD = 4;
const int CMD_DEL = 11;
const int CMD_LS = 12;
const int CMD_CD = 13;
@ -218,8 +220,9 @@ const int CMD_PWD = 19;
const int CMD_RMDIR = 15;
const int CMD_TEST = 20;
const int ERR_RESPONSE = 255;
const int OK_RESPONSE = 0;
const int ERR_RESPONSE = 255;
const int WAIT_RESPONSE = 1;
const int OK_RESPONSE = 0;
char filename[64];
char tmp[64];
@ -260,6 +263,8 @@ void setup() {
last_dir = -1; // no previous data direction
set_data_port_direction(DIR_INPUT);
/*
// regex disabled for now
MatchState ms;
char buf [100] = { "The quick " };
ms.Target (buf);
@ -267,6 +272,7 @@ void setup() {
if(result >0) {
Serial.println("match!");
}
*/
// set working directory to root
strcpy(cd_path, "/");
@ -400,6 +406,7 @@ void print_dir_entry(File dir, int list_files, int command) {
// send file size or directory
entry.getName(filename, 64);
strtoupper(filename);
if((list_files == 0 && entry.isDirectory()) || (list_files == 1 && !entry.isDirectory())) {
if(entry.isDirectory()) {
@ -429,16 +436,42 @@ void print_dir_entry(File dir, int list_files, int command) {
send_byte_to_cpu('\r');
}
else {
// BUG the following line does not work
// sprintf(tmp, "%5d %s", entry.size(), filename);
// use this instead
char type[5];
char address[5];
strcpy(type,"");
strcpy(address, "");
char *x = strchr(filename, '#');
if(x != NULL) {
*x++ = 0;
if(x[0]=='0' && x[1]=='6') {
strcpy(type,"BIN ");
strcpy(address,x+2);
}
else if(x[0]=='F' && x[1]=='1') {
strcpy(type,"BAS ");
strcpy(address,x+2);
}
else {
strcpy(type,"??? ");
}
}
sprintf(tmp, "%-15s", filename);
print_string_to_cpu(tmp);
Serial.print(filename);
sprintf(tmp, "%6d", entry.size());
sprintf(tmp, "%6d ", entry.size());
print_string_to_cpu(tmp);
Serial.println(tmp);
Serial.print(tmp);
print_string_to_cpu(type);
Serial.print(type);
print_string_to_cpu(address);
Serial.println(address);
send_byte_to_cpu('\r');
}
@ -616,6 +649,173 @@ void comando_write() {
send_byte_to_cpu(OK_RESPONSE);
}
// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_LOAD ******************************************
// **************************************************************************************
// **************************************************************************************
void comando_load() {
Serial.println(F("command CMD_LOAD received from CPU"));
// reads filename as 0 terminated string
receive_string_from_cpu(filename);
if(TIMEOUT) return;
Serial.print(F("file to read: "));
Serial.println(filename);
// if a matching file name is found, use it
if(matchname(filename, tmp)==1) {
Serial.print(F("found matching file: "));
Serial.println(tmp);
strcpy(filename, tmp);
}
if(!SD.exists(filename)) {
Serial.println(F("error opening file"));
send_byte_to_cpu(ERR_RESPONSE);
send_string_to_cpu(FILE_NOT_FOUND);
return;
}
// open the file
File myFile = SD.open(filename);
if(!myFile) {
Serial.println(F("error opening file"));
send_byte_to_cpu(ERR_RESPONSE);
send_string_to_cpu(CANT_OPEN_FILE);
return;
}
Serial.println(F("file opened on the SD card"));
// ok response
send_byte_to_cpu(OK_RESPONSE);
if(TIMEOUT) return;
Serial.println(F("ok response sent to CPU"));
// sends matched filename
send_string_to_cpu(filename);
// sends size as low and high byte
int size = myFile.size();
send_byte_to_cpu(size & 0xFF);
send_byte_to_cpu((size >> 8) & 0xFF);
if(TIMEOUT) return;
Serial.println(F("file size sent to CPU"));
int bytes_sent = 0;
while(myFile.available() && !TIMEOUT) {
send_byte_to_cpu(myFile.read());
if(!TIMEOUT) bytes_sent++;
}
myFile.close();
if(TIMEOUT) {
Serial.print(F("timeout, bytes sent: "));
Serial.println(bytes_sent);
return;
}
Serial.println(F("file read ok"));
}
void strtoupper(char *str){
int len = strlen(str), i;
for(i=0;i<len;i++)
if(str[i]>='a' && str[i]<='z')
str[i] = str[i]-'a'+'A';
}
// splits filename into file_path and file_name
// e.g. "root/myfolder/pluto" => "root/myfolder" , "pluto"
// "myfolder/pluto" => "myfolder", "pluto"
// "pluto" => "", "pluto"
// "/pluto" => "/", "pluto"
char file_path[64];
char file_name[64];
void split_path(char *filename) {
strcpy(file_path, filename);
for(int t=strlen(file_path)-1;t>0;t--) {
if(file_path[t] == '/') {
file_path[t] = 0;
strcpy(file_name, &file_path[t+1]);
if(t==0) strcpy(file_path, "/"); // case of root folder
return;
}
}
strcpy(file_path, "");
strcpy(file_name, filename);
}
//
// returns in dest the first file that matches (starts with) "filename"
// returns 1 if matching file is found
// returns 0 if no matching file is found
//
int matchname(char *filename, char *dest) {
// split filename into file_path and file_name
split_path(filename);
Serial.print(F("after split file_path="));
Serial.print(file_path);
Serial.print(F(", file_name="));
Serial.println(file_name);
if(strlen(file_path)==0) Serial.println(F("scanning the current directory"));
else Serial.println(F("scanning the file_path directory"));
// open the directory containing the file
File dir = SD.open(strlen(file_path)==0 ? cd_path : file_path);
if(!dir) return 0;
Serial.println(F("dir opened"));
// scan all the directory
while(1) {
send_byte_to_cpu(WAIT_RESPONSE);
if(TIMEOUT) return 0;
File F = dir.openNextFile();
// end of directory
if(!F) {
dir.close();
return 0;
}
// copy filename in dest and closes it
F.getName(dest, 64);
strtoupper(dest);
F.close();
Serial.println(F("file entry:"));
Serial.println(dest);
// verify the match
if(strncmp(file_name, dest, strlen(filename))==0) {
Serial.println(F("it is matching!"));
// matching, dest already contains the matched file name
// if file_path is empty then it's current directory, do nothing
// else file_path needs to be combined with file name
if(file_path[0]!=0) {
Serial.println(F("not on current dir, joining paths"));
strcpy(filename, dest);
if(file_path[0]=='/' && file_path[1]==0) sprintf(dest,"/%s", filename); // case of root folder
else sprintf(dest,"%s/%s", file_path, filename); // case of normal nested folder
}
dir.close();
return 1;
}
}
}
// **************************************************************************************
// **************************************************************************************
// ********************************* CMD_DEL ********************************************
@ -867,6 +1067,7 @@ void loop() {
if(data == CMD_READ) comando_read();
else if(data == CMD_WRITE) comando_write();
else if(data == CMD_LOAD) comando_load();
else if(data == CMD_DEL) comando_del();
else if(data == CMD_RMDIR) comando_rmdir();
else if(data == CMD_MKDIR) comando_mkdir();

View File

@ -6,30 +6,109 @@
// global cmd
void comando_load() {
void comando_load_bas() {
// send command byte
send_byte_to_MCU(CMD_READ);
send_byte_to_MCU(CMD_LOAD);
if(TIMEOUT) return;
// send filename
send_string_to_MCU(filename);
if(TIMEOUT) return;
// response
byte response = receive_byte_from_MCU();
if(TIMEOUT) return;
// wait for OK response, MCU sends many WAIT_RESPONSE to avoid TIMEOUT
while(1) {
byte response = receive_byte_from_MCU();
if(TIMEOUT) return;
if(response == ERR_RESPONSE) {
// error with file, print message
print_string_response();
return;
if(response == ERR_RESPONSE) {
// error with file, print message
print_string_response();
return;
}
if(response == OK_RESPONSE) break;
}
// get matching file name
receive_string_from_MCU(filename);
woz_puts("FOUND ");
woz_puts(filename);
woz_putc('\r');
byte filetype = 0;
token_ptr = filename;
while(1) {
if(*token_ptr == '#') {
if(token_ptr[1] == '0' && token_ptr[2] == '6') { filetype = 0x06; break; }
if(token_ptr[1] == 'F' && token_ptr[2] == '1') { filetype = 0xF1; break; }
}
if(*token_ptr == 0) break;
token_ptr++;
}
// calculate start address for 0x06 binary file
if(filetype == 0x06) {
token_ptr+=2;
hex_to_word(token_ptr);
start_address = tmpword;
}
// get file length in tmpword
receive_word_from_mcu();
if(TIMEOUT) return;
if(filetype != 0x06 && filetype != 0xF1) {
woz_puts("?INVALID FILE NAME TAG #");
for(word t=0;t!=tmpword;t++) receive_byte_from_MCU(); // empty buffer
return;
}
woz_puts("LOADING\r");
if(filetype == 0x06) {
// 0x06 BINARY FILE format
// get file bytes
token_ptr = (byte *) start_address;
for(word t=0;t!=tmpword;t++) {
byte data = receive_byte_from_MCU();
if(TIMEOUT) return;
*token_ptr++ = data;
#ifdef LOADING_DOTS
if(((byte)t) == 0) woz_putc('.');
#endif
}
// decrease by one for display result
token_ptr--;
// print feedback to user
woz_putc('\r');
woz_puts(filename);
woz_putc('\r');
woz_print_hexword(start_address);
woz_putc('.');
woz_print_hexword((word)token_ptr);
woz_puts(" (");
utoa(tmpword, filename, 10); // use filename as string buffer
woz_puts(filename);
woz_puts(" BYTES)\rOK");
// executes machine language program at start address
if(cmd == CMD_RUN) {
woz_putc('\r');
tmpword = start_address;
asm {
jmp (tmpword)
}
}
return;
}
// 0xF1 BASIC FILE TYPE
// get file bytes
token_ptr = (byte *) 0;
for(word t=0;t!=tmpword;t++) {
@ -37,7 +116,9 @@ void comando_load() {
if(TIMEOUT) return;
if((t==0 && data!=0x41) || (t==1 && data!=0x31)) {
woz_puts("?NOT CFFA1/PRODOS FORMAT");
woz_puts("?UNKNOWN FILE FORMAT");
t=t+1;
for(;t!=tmpword;t++) receive_byte_from_MCU(); // empty buffer
return;
}
else if(t<0x004a) {
@ -81,14 +162,15 @@ void bas_file_info() {
// print feedback to user
woz_putc('\r');
woz_puts(filename);
woz_puts(": ");
woz_putc('\r');
bas_info();
woz_puts("\rOK");
}
void bas_info() {
woz_puts("LOMEM=");
woz_puts("(LOMEM=");
woz_print_hexword((word) *BASIC_LOMEM);
woz_puts(" HIMEM=");
woz_print_hexword((word) *BASIC_HIMEM);
woz_putc(')');
}

View File

@ -50,7 +50,7 @@ void comando_read() {
// print feedback to user
woz_putc('\r');
woz_puts(filename);
woz_puts(": ");
woz_putc('\r');
woz_print_hexword(start_address);
woz_putc('.');
woz_print_hexword((word)token_ptr);

View File

@ -1,11 +1,20 @@
// global len
void comando_save() {
void comando_save_bas() {
if(((word) *BASIC_HIMEM) < ((word) *BASIC_LOMEM)) {
woz_puts("?NO BASIC PROGRAM");
return;
}
// send command byte
send_byte_to_MCU(CMD_WRITE);
if(TIMEOUT) return;
strcat(filename, "#F1");
tmpword = (word) *BASIC_LOMEM;
append_hex_tmpword(filename);
// send filename
send_string_to_MCU(filename);
if(TIMEOUT) return;
@ -19,6 +28,8 @@ void comando_save() {
return;
}
woz_puts("SAVING\r");
// send file size
//tmpword = ((word) *BASIC_HIMEM) - ((word)*BASIC_LOMEM) + 512;
// in assembly:

View File

@ -60,7 +60,7 @@ void comando_write() {
// print feedback to user
woz_putc('\r');
woz_puts(filename);
woz_puts(": ");
woz_puts(":\r");
woz_print_hexword(start_address);
woz_putc('.');
woz_print_hexword(end_address);

View File

@ -1,5 +1,5 @@
#include <stdlib.h>
#include <stdlib.h>
#include <string.h>
byte **const BASIC_LOMEM = (byte **) 0x004a; // lomem pointer used by integer BASIC
byte **const BASIC_HIMEM = (byte **) 0x004c; // himem pointer used by integer BASIC
@ -15,8 +15,9 @@ byte *const filename = (byte *) (KEYBUFSTART+KEYBUFLEN+6 ); // [33] stores a
byte *const hex1 = (byte *) (KEYBUFSTART+KEYBUFLEN+6+33 ); // [5] stores a hex parameter
byte *const hex2 = (byte *) (KEYBUFSTART+KEYBUFLEN+6+33+5); // [5] stores a hex parameter
const byte OK_RESPONSE = 0x00;
const byte ERR_RESPONSE = 0xFF;
const byte OK_RESPONSE = 0x00;
const byte WAIT_RESPONSE = 0x01;
const byte ERR_RESPONSE = 0xFF;
// command constants, which are also byte commands to send to the MCU
const byte CMD_READ = 0;
@ -127,6 +128,27 @@ void hex_to_word(byte *str) {
if(i>4 || i==0) hex_to_word_ok = 0;
}
void strcat(char *dest, char *src) {
while(*dest) dest++;
while(*src) *dest++ = *src++;
*dest = 0;
}
void append_hex_digit(char *dest, byte digit) {
while(*dest) dest++;
if(digit<10) digit += '0';
else digit += 'A' - 10;
*dest++ = digit;
*dest = 0;
}
void append_hex_tmpword(char *dest) {
append_hex_digit(dest, *((byte *)&tmpword+1) >> 4);
append_hex_digit(dest, *((byte *)&tmpword+1) & 0x0F);
append_hex_digit(dest, *((byte *)&tmpword+0) >> 4);
append_hex_digit(dest, *((byte *)&tmpword+0) & 0x0F);
}
#include "cmd_read.h"
#include "cmd_write.h"
#include "cmd_load.h"
@ -238,7 +260,7 @@ void console() {
woz_puts("?MISSING FILENAME");
continue;
}
comando_load();
comando_load_bas();
}
else if(cmd == CMD_SAVE) {
get_token(filename, 32); // parse filename
@ -246,7 +268,33 @@ void console() {
woz_puts("?MISSING FILENAME");
continue;
}
comando_save();
//comando_save();
get_token(hex1, 4);
if(hex1[0] != 0) {
// it's SAVE binary file
hex_to_word(hex1);
if(!hex_to_word_ok) {
woz_puts("?BAD ADDRESS");
continue;
}
start_address = tmpword;
strcat(filename, "#06");
append_hex_tmpword(filename);
get_token(hex2, 4);
hex_to_word(hex2);
if(!hex_to_word_ok) {
woz_puts("?BAD ADDRESS");
continue;
}
end_address = tmpword;
comando_write();
}
else {
comando_save_bas();
}
}
else if(cmd == CMD_TYPE) {
get_token(filename, 32); // parse filename

View File

@ -121,6 +121,16 @@ void send_string_to_MCU(char *msg) {
}
}
// receive a string sent by the MCU
void receive_string_from_MCU(char *dest) {
while(1) {
byte data = receive_byte_from_MCU();
*dest++ = data;
if(TIMEOUT) break;
if(data == 0) break; // string terminator
}
}
// print a string sent by the MCU
void print_string_response() {
while(1) {