Components and supplies
Level Shifter Board
Hex Schmitt-Trigger Inverter
Breadboard (generic)
BBC Micro model B (Acorn Computers)
27C256 EPROM
Jumper wires (generic)
Arduino MKR1000
Project description
Code
ArduinoHost.ino
arduino
Enables a BBC Micro to view an Arduino as a file system host over a serial connection. Arduino connects to wifi, enabling the BBC Micro to mount a .ssd DFS disc image over the Internet.
1// Arduino Host - 8bitkick 2// ------------------------ 3// 4// Storage medium is BBC Micro DFS disc images (.ssd) served from bbcmicro.co.uk 5// 6// 7// Reference 8// 9// HostFS - Sweh 10// github.com/sweharris/TubeHost/blob/master/TubeHost 11// 12// Arduino-UPURS I/O - Myelin 13// github.com/google/myelin-acorn-electron-hardware/blob/master/upurs_usb_port/upurs_usb_port.ino 14// 15 16 17#include <Arduino.h> 18#include "wiring_private.h" 19#include <SPI.h> 20#include <WiFi101.h> 21#include <FlashStorage.h> 22 23#define UPURSFSv008 // Boot behavior differs from v0.03 24 25#define DEBUG 26 27 28// WIFI & WEB 29 30char ssid[] = SECRET_SSID; 31char pass[] = SECRET_PASS; 32int status = WL_IDLE_STATUS; 33 34WiFiClient client; 35 36#define TCPPORT 80 37#define MAX_WEBLINKS 20 38 39String webhost = "www.bbcmicro.co.uk"; 40String webdisc = "/gameimg/discs/79/Disc005-DareDevilDenis.ssd"; // This is effectively our current URL 41String weblink[MAX_WEBLINKS]; // Here we cache hyperlinks from searching the given webhost 42int weblinks = 0; 43 44 45// SERIAL WIRE DEFINES 46 47#define USE_ARDUINO_MKR // We assume RX & TX signals are inverted externally as we can't use SofwareSerial. 48// NB Must use 5v <> 3.3v level shifter with Arduino MKR 49 50#define TXD_PIN 0 // Serial transmit 51#define RXD_PIN 1 // Serial receive 52 53#define RTS_PIN 3 // active-high output Requesting them To Send. We are always ready so this stays high 54#define CTS_PIN 4 // active-high input tells us we're Cleared To Send. (Using 4 as we can attach interrupt) 55 56#define LED_PIN 6 57 58 59// TUBE HOST COMMAND DEFINES 60// mdfs.net/Software/Tube/Serial/Protocol 61 62#define ESCAPE 0x7F 63#define OSRDCHIO 0x00 // Cy A 64#define OSCLI 0x02 // string 0x0D 0x7F or 0x80 65#define OSBYTELO 0x04 // X A X 66#define OSBYTEHI 0x06 // X Y A Cy Y X 67#define OSWORD 0x08 // A in_length block out_length block 68#define OSWORD0 0x0A // block 0xFF or 0x7F string 0x0D 69#define OSARGS 0x0C // Y block A A block 70#define OSBGET 0x0E // Y Cy A 71#define OSBPUT 0x10 // Y A 0x7F 72#define OSFIND 0x12 // 0x00 Y 0x7F 73#define OSFIND 0x12 // A string 0x0D A 74#define OSFILE 0x14 // block string 0x0D A A block 75#define OSGBPB 0x16 // block A block Cy A 76#define OSFSC 0x18 // X Y A 0xFF Y X or 0x7F 77 78int long timer = 0; 79 80// DISC 81// http://beebwiki.mdfs.net/Acorn_DFS_disc_format 82 83 84#define CAT_LENGTH 512 85 86// Boot option :- 87// Bit 5 Bit 4 Action 88// 0 0 No action 89// 0 1 *LOAD $.!BOOT 90// 1 0 *RUN $.!BOOT 91// 1 1 *EXEC $.!BOOT 92 93struct DFS_Disc 94{ 95 // DFS catalogue information 512 bytes (Sectors 0 & 1) 96 char volumeLo[8]; 97 char filename[31][8]; 98 99 char volumeHi[4]; 100 uint8_t cycle; 101 uint8_t numberFiles; 102 uint8_t bootOption; 103 uint8_t sectors; 104 105 struct Fileinfo{ 106 uint16_t loadAddr; 107 uint16_t execAddr; 108 uint16_t fileLen; 109 uint8_t bits; 110 uint8_t startSector; 111 }fileinfo[31]; 112}; 113 114DFS_Disc disc; 115 116 117/* TODO - implement defines for bitfield derived DFS values 118// from DFS.c - Dominic Beesley 119#define DFS_SECTORCOUNT (unsigned int)((dfs->header.sector1.seccount_l \\ 120+ ((dfs->header.sector1.opt4 & 3) << 8)) ) 121 122#define DFS_CATCOUNT(dfs) (dfs->header.sector1.cat_count >> 3) 123 124#define DFS_FILE_LOCKED(dfs, index) \\ 125( (dfs->header.sector0.file_names[index].dir & 0x80) != 0) 126 127#define DFS_EX(x) ( ((x & 0x30000) == 0x30000)? x | 0xFC0000:x) 128 129#define DFS_FILE_LOAD(dfs, index) DFS_EX(( dfs->header.sector1.file_addrs[index].load_l \\ 130+ (dfs->header.sector1.file_addrs[index].load_h << 8) \\ 131+ ( (unsigned long int) (dfs->header.sector1.file_addrs[index].morebits & 0x0C) << 14) )) 132 133#define DFS_FILE_LEN(dfs, index) ( dfs->header.sector1.file_addrs[index].len_l \\ 134+ (dfs->header.sector1.file_addrs[index].len_h << 8) \\ 135+ ( (unsigned long int) (dfs->header.sector1.file_addrs[index].morebits & 0x30) << 12) ) 136 137#define DFS_FILE_EXEC(dfs, index) DFS_EX(( dfs->header.sector1.file_addrs[index].exec_l \\ 138+ (dfs->header.sector1.file_addrs[index].exec_h << 8) \\ 139+ ( ((unsigned long int) dfs->header.sector1.file_addrs[index].morebits & 0xC0) << 10) )) 140 141#define DFS_FILE_SECTOR(dfs, index) ( dfs->header.sector1.file_addrs[index].secaddr_l \\ 142+ ( (dfs->header.sector1.file_addrs[index].morebits & 0x03 ) << 8) ) 143*/ 144 145 146// Teletext colour codes 147#define TT_RED char(129) 148#define TT_GREEN char(130) 149#define TT_YELLOW char(131) 150#define TT_BLUE char(132) 151#define TT_MAGENTA char(133) 152#define TT_CYAN char(134) 153#define TT_WHITE char(135) 154 155 156// ========================================================================== 157// SECTION: Arduino Serial port 158// ========================================================================== 159 160 161// Fast GPIO access for MKR1000 162#ifdef USE_ARDUINO_MKR 163 164inline void digitalWrite_fast(int pin, bool val) { if (val) 165 PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg = (1ul << g_APinDescription[pin].ulPin); 166 else 167 PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg = (1ul << g_APinDescription[pin].ulPin); 168} 169 170inline int digitalRead_fast(int pin) { 171 return !!(PORT->Group[g_APinDescription[pin].ulPort].IN.reg & (1ul << g_APinDescription[pin].ulPin)); 172} 173 174#define CTS_IS_ACTIVE (digitalRead_fast(CTS_PIN) == HIGH) 175 176// Using SAMD21 SERCOM for UART instances 177Uart SerialBBC (&sercom3, RXD_PIN, TXD_PIN, SERCOM_RX_PAD_1, UART_TX_PAD_0); // Create the new UART instance assigning it to pin 0 and 1 178void SERCOM3_Handler() { 179 SerialBBC.IrqHandler(); 180} 181#endif USE_ARDUINO_MKR 182 183 184// Keep track of CTS timing with this interrupt handler (micros() OK if we're quick about it....) 185volatile unsigned long CTS_since = 0; 186void CTSchanged() {CTS_since = micros();} 187 188void setup_serial() { 189 190 // Using SAMD21 SERCOM for UART instances 191 #ifdef USE_ARDUINO_MKR 192 pinPeripheral(RXD_PIN, PIO_SERCOM); //Assign RX function to pin 0 193 pinPeripheral(TXD_PIN, PIO_SERCOM); //Assign TX function to pin 1 194 #endif USE_ARDUINO_MKR 195 196 // SET UP SERIAL 197 Serial.begin(115200); 198 SerialBBC.begin(115200); 199 200 pinMode(RTS_PIN, OUTPUT); 201 digitalWrite(RTS_PIN, HIGH); 202 pinMode(CTS_PIN, INPUT); 203 attachInterrupt(digitalPinToInterrupt(CTS_PIN), CTSchanged, RISING); 204} 205 206 207// Implement hardware flow control by monitoring CTS 208void raw_write(byte byteOut) { 209 while ( 210 !( 211 CTS_IS_ACTIVE && // We must be clear to send 212 (micros() - CTS_since > 3) && // for at least 3us 213 (micros() - CTS_since < 20) // and last successful byte transfer <20us ago 214 ) 215 ){}; // Otherwise wait for this condition 216 SerialBBC.write(byteOut); 217 218 if (CTS_IS_ACTIVE) { 219 CTS_since = micros()+3; 220 } 221} 222 223int long raw_read() {while (SerialBBC.available() == 0){};return SerialBBC.read();} 224 225 226// ========================================================================== 227// SECTION: Serial I/O routines for communication with Client 228// ========================================================================== 229 230 231int long read_addr() {uint32_t addr = 0;for( int a = 0; a < 4; a++ ){addr = addr * 256 + raw_read();}return addr;} 232 233void send_byte(byte byteOut) {raw_write(byteOut);if (byteOut==ESCAPE) {raw_write(ESCAPE);};} 234 235void send_cmd(byte byteOut) {raw_write(ESCAPE);raw_write(byteOut);} 236 237void send_addr(uint32_t addr) {send_byte(addr >> 24);send_byte((addr >> 16) & 0xFF);send_byte((addr >> 8) & 0xFF);send_byte(addr & 0xFF);} 238 239void send_data(uint8_t *data, int length) { 240 for(int n=0; n < length; n++) { 241 raw_write(data[n]); 242 } 243} 244 245void send_string(String message) { 246 unsigned int len = message.length(); 247 for(int n=0; n < len; n++) { 248 send_byte(message[n]); 249 } 250}; 251 252void send_reply(String message) { 253 unsigned int len = message.length(); 254 for(int n=0; n < len; n++) { 255 send_byte(message[n]); 256 } 257 send_byte(0x00); 258}; 259 260void send_output(String message) { 261 send_byte(0x40); 262 send_reply(message); 263}; 264 265String read_string() { 266 byte nextByte = 0; int index = 0; char stringbuff[80]; 267 while (nextByte != 0x0D && index<80){ 268 if (nextByte != 0) {stringbuff[index++] = nextByte; 269 } 270 nextByte = raw_read(); 271 } 272 stringbuff[index] = 0; 273 274 return stringbuff; 275} 276 277String extract_filename(String data) 278{ 279 String filename; 280 int i = 0; 281 282 while (data[i] != 0x20 && data[i] != 0x00 && i < data.length()) { 283 if (data[i] != 34) {filename = filename + data[i];}; // REMOVE " FROM FILENAME 284 i++; 285 } 286 return filename; 287} 288 289String extract_parameter(String data) 290{ 291 String ret; 292 int i = 0; 293 294 while (data[i] != 0x20 && data[i] != 0x00 && i < data.length()) { 295 i++; 296 } 297 i++; 298 while (data[i] != 0x20 && data[i] != 0x00 && i < data.length()) { 299 if (data[i] != 34) {ret = ret + data[i];}; // REMOVE " FROM FILENAME 300 i++; 301 } 302 return ret; 303} 304 305void error(uint8_t code, const char* message) { 306 send_cmd(0); 307 send_byte(code); 308 send_reply(message); 309} 310 311void send_RLE_image(uint32_t loadAddr, uint8_t *data, int length) { 312 send_cmd(0xE0); // Load data response 313 send_addr(loadAddr); // Address to load to 314 int bytes = 0; 315 for(int n=0; n < length; n=n+2) { // Data 316 int value = data[n]; 317 int runlength = data[n+1]; 318 for(int x=0; x < runlength; x++) { 319 bytes++; 320 send_byte(value); 321 } 322 } 323 send_cmd(0xB0); // End 324} 325 326// BBC LOAD DATA 327void send_data_to_memory(uint32_t loadAddr, uint8_t *data, int length) { 328 send_cmd(0xE0); // Load data response 329 send_addr(loadAddr); // Address to load to 330 send_data(data, length); // Data 331 send_cmd(0xB0); // End 332} 333 334// BBC SAVE DATA 335void read_data_from_memory(uint32_t saveAddr, int length) { 336 send_cmd(0xF0); 337 send_addr(saveAddr); 338 for(int n=0; n < length; n=n+1) { 339 const byte byteIn = raw_read(); 340 Serial.write(byteIn); 341 } 342 send_cmd(0xB0); 343 Serial.print(length,DEC); 344 Serial.println(" bytes saved\ 345"); 346} 347 348// ========================================================================== 349// SECTION: OSFSC 350// ========================================================================== 351 352 353void runOSFSC() { 354 byte X = raw_read(); byte Y = raw_read(); byte A = raw_read(); 355 char fname [10]; 356 switch (A) { 357 /* 358 case 0: // OPT Handler 359 case 1: // Check EOF 360 case 2: // *Handler 361 case 6: // Shutdown files 362 case 7: // Give file handle ranges 363 case 8: // *ENABLE checker */ 364 case 3: // Unrecognised * command handler 365 OSFSC_cmd(X,Y); 366 break; 367 // *RUN handler 368 case 4: { 369 send_byte(127); // Request filename 370 String filename = extract_filename(read_string()); 371 int fileID = DFS_find(filename); 372 if (fileID != -1) { 373 send_cmd(0xE0); // Start data send 374 send_addr(disc.fileinfo[fileID].loadAddr); // Address to load to 375 web_load(fileID); // Send file data 376 send_cmd(0xB0); // End 377 send_cmd(0xc0); // set execution address 378 send_addr(disc.fileinfo[fileID].execAddr); 379 send_byte(0x80); // do it! 380 } else { 381 error(214,"file not found"); 382 } 383 } 384 break; 385 // *CAT handler 386 case 5: { 387 String response = DFS_cat(); 388 send_byte(127); 389 send_byte(0x40); 390 send_string(response); 391 send_byte(0); 392 } 393 break; 394 // BOOT (See https://github.com/sweharris/TubeHost ) 395 case 255: 396 send_byte(255); send_byte(0);send_byte(X); 397 setup_wifi(); // We restart wifi on boot 398 // web_mount(); // DEBUG 399 break; 400 default: 401 error(214,"ArduinoHost: unhandled OSFSC"); 402 Serial.print("Unhandled OSFSC A=0x"); 403 Serial.print(A, HEX); 404 break; 405 } // end of switch 406} 407 408// Handle custom * commands here 409// ----------------------------- 410void OSFSC_cmd (uint8_t X, uint8_t Y){ 411 send_byte(ESCAPE); // tell client to send us the command 412 String commandline = read_string(); 413 String command = extract_filename(commandline); 414 String parameter = extract_parameter(commandline); 415 416 if (command=="WIFI") { 417 setup_wifi(); 418 if (web_mount()==0) { 419 send_byte(0x40); // Reponse start 420 send_wifi_status(); 421 send_byte(0x00); // Reponse end 422 return; 423 } 424 } 425 426 if (command=="SEARCH") { // Search host 427 web_search(parameter); 428 return; 429 } 430 /* 431 if (command=="HOST") { // Change / display host 432 if (parameter!=""){ 433 webhost=parameter; 434 } 435 send_byte(0x40); // Host FS reponse start 436 send_string(String(TT_RED)+webhost+"\ \ 437"); 438 send_byte(0x00); // Host FS reponse end 439 440 return; 441 }*/ 442 443 if (command=="MOUNT") { 444 webdisc = "/gameimg/discs/"+weblink[parameter.toInt()]+".ssd"; // TODO - catch dsd 445 web_mount(); // TODO catch error 446 String cat = DFS_cat(); 447 send_byte(0x40); // Host FS reponse start 448 send_string(String(TT_YELLOW)+webdisc+"\ \ 449"+cat); // Host FS send string 450 send_byte(0x00); // Host FS reponse end 451 452 return; 453 } 454 455 error(214,"ArduinoHost: unhandled * command"); 456} 457// ========================================================================== 458// SECTION: OSFILE 459// ========================================================================== 460 461void runOSFILE() { 462 uint32_t end = read_addr(); 463 uint32_t save = read_addr(); 464 uint32_t exec = read_addr(); 465 uint32_t load = read_addr(); 466 String filename = extract_filename(read_string()); 467 byte A = raw_read(); 468 switch (A) { 469 case 0: // SAVE DATA 470 timer = micros(); 471 read_data_from_memory(save, end-save); 472 timer = micros() - timer; 473 Serial.println("*SAVE " + filename); 474 Serial.print(timer, DEC); 475 Serial.println("us "); 476 break; 477 case 255: { // LOAD DATA 478 int fileID = DFS_find(filename); 479 if (fileID != -1){ 480 timer = micros(); 481 if (load==0) {load = disc.fileinfo[fileID].loadAddr;}; 482 send_cmd(0xE0); // Start load data 483 send_addr(load); // Address to load to 484 web_load(fileID);// Load data 485 send_cmd(0xB0); // End 486 timer = micros() - timer; 487 Serial.println("*LOAD " + filename); 488 Serial.print(timer, DEC); 489 Serial.println("us "); 490 } else { 491 error(214,"file not found"); 492 } 493 break; 494 } 495 default: 496 error(214,"Unhandled OSFILE"); 497 Serial.print("Unhandled OSFILE A=0x"); 498 Serial.print(A, HEX); 499 break; 500 } // end of switch 501 // Send results 502 send_byte(1); // File found; directories aren't possible 503 send_addr(end); 504 send_addr(save); 505 send_addr(exec); 506 send_addr(load); 507} 508 509// Replace with jump table 510void runCommand (const byte inByte) { 511 switch (inByte) { 512 case OSFSC: runOSFSC(); 513 break; 514 case OSFILE: runOSFILE(); 515 break; 516 default: { 517 Serial.print("Unhandled command 0x"); 518 Serial.print(inByte, HEX); 519 } 520 break; 521 } // end of switch 522} 523 524// ========================================================================== 525// SECTION: Wifi & http get 526// ========================================================================== 527 528String send_wifi_status() { 529 if (status != WL_CONNECTED) { 530 // Connect to WPA/WPA2 network. Change this line if using open or WEP network: 531 status = WiFi.begin(ssid, pass); 532 send_string("Connected to wifi\ \ 533"); 534 } else 535 { 536 uint32_t myAddr = WiFi.localIP(); 537 String first_octet = String((myAddr >> 24) & 0xFF); 538 byte second_octet = (myAddr >> 16) & 0xFF; 539 byte third_octet = (myAddr >> 8) & 0xFF; 540 byte fourth_octet = myAddr & 0xFF; 541 String localIP = first_octet + "." + second_octet + "." + third_octet + "." + fourth_octet; 542 send_string( "Wifi connected"); 543 send_string("\ \ 544Local IP: "); 545 send_string(localIP); 546 send_string("\ \ 547Webhost : "+webhost); 548 send_string("\ \ 549"); 550 } 551} 552 553void setup_wifi(){ 554 // Edit this section for static IP, remove for DHCP 555 IPAddress ip(172, 20, 20, 61); 556 IPAddress subnet(255, 255, 255, 0); 557 IPAddress dns(75, 75, 75, 75); 558 IPAddress gateway(172, 20, 20, 1); 559 WiFi.config(ip, dns, gateway, subnet); 560 // check for the presence of the shield 561 if (WiFi.status() == WL_NO_SHIELD) { 562 Serial.println("WiFi shield not present"); 563 // don't continue: 564 while (true); 565 } 566 567 // attempt to connect to Wifi network: 568 while (status != WL_CONNECTED) { 569 Serial.print("Attempting to connect to SSID: "); 570 Serial.println(ssid); 571 // Connect to WPA/WPA2 network. Change this line if using open or WEP network: 572 status = WiFi.begin(ssid, pass); 573 // wait 1 seconds for connection: 574 delay(3000); 575 } 576 Serial.println("Connected to wifi"); 577} 578 579// GET 580int http_get(String host, String path, int start, int end) { 581 client.stop(); 582 String range = "Range: bytes="; // NB - WE REQUIRE WEBSERVER TO SUPPORT PARTIAL CONTENT 583 if (start != 0) {range = range + start;} else {range = range + "0";} 584 range = range + "-"; 585 range = range + end; 586 Serial.println(range); 587 // if there's a successful connection: 588 if (client.connect(host.c_str(), TCPPORT)) { 589 client.println("GET " + path + " HTTP/1.1"); 590 client.println("Host: "+ host); 591 if (start != end) {client.println(range);} // Skip range request if start=end 592 client.println("Connection: close"); 593 client.println(); 594 // Check response 595 char status[32] = {0}; 596 client.readBytesUntil('\ ', status, sizeof(status)); 597 if (strcmp(status, "HTTP/1.1 206 Partial Content") != 0) { 598 Serial.print(F("Unexpected response: ")); 599 Serial.println(status); 600 return 1; 601 } 602 char endOfHeaders[] = "\ \ 603\ \ 604"; 605 if (!client.find(endOfHeaders)) { 606 Serial.println(F("Invalid response")); 607 return 1; 608 } 609 return 0; 610 } 611} 612 613char client_read(){ 614 while (!client.available()){}; 615 return client.read(); 616}; 617 618char client_empty(){ 619 while (client.available()){ 620 Serial.write(client.read()); 621 } // TODO test for unused data recieved 622} 623 624// ========================================================================== 625// SECTION: HOST MEDIUM - WEB-BASED BBC MICRO DFS DISC IMAGES (.ssd) 626// ========================================================================== 627// Take filename and return filehandle (relative pointer to DFS catalogue entry) 628 629String DFS_filename(int fileID) { 630 char filename[10]; 631 filename[0] = disc.filename[fileID][7] & 0x7F; 632 filename[1] = '.'; 633 memcpy(filename+2, disc.filename[fileID], 7); 634 for (int i=0; i<9; i++) { 635 if (filename[i] <= 32) 636 filename[i] = 0; 637 } 638 filename[9] = 0; 639 return String(filename); 640} 641 642String DFS_volume() { 643 char volume[13]; 644 memcpy(volume, disc.volumeLo, 8); 645 memcpy(volume+8, disc.volumeHi, 4); 646 for (int i=0; i<12; i++) { 647 if (volume[i] <= 32) 648 volume[i] = 0; 649 } 650 volume[12] = 0; 651 return String(volume); 652} 653 654 655int DFS_find(String filename){ 656 int id = -1; 657 if (filename.indexOf('.') != 1) {filename = "$."+filename;} 658 for( int a = 0; a < (disc.numberFiles >> 3); a = a + 1 ){ 659 String thisfile = DFS_filename(a); 660 if (filename.equalsIgnoreCase(thisfile)) {id = a;} 661 } 662 return id; 663} 664 665String DFS_cat(){ 666 // Volume title 667 String cat; 668 uint8_t files = disc.numberFiles >> 3; // number of files 669 cat = " "+String(TT_YELLOW) + "\ \ 670Volume title: "+DFS_volume()+" \ \ 671"; 672 673 for( int a = 0; a < files; a = a + 1 ){ 674 675 cat = cat + DFS_filename(a); 676 cat = cat + " "; 677 cat = cat + String(disc.fileinfo[a].loadAddr, HEX); 678 cat = cat + " "; 679 cat = cat + String(disc.fileinfo[a].execAddr, HEX); 680 cat = cat + " "; 681 cat = cat + String(disc.fileinfo[a].fileLen, HEX); 682 cat = cat + " "; 683 cat = cat + String(disc.fileinfo[a].startSector, HEX); 684 cat = cat + "\ \ 685"; 686 } 687 Serial.println(cat); 688 return cat; 689} 690 691String get_string(int len){ 692 String output = ""; 693 for( int a = 0; a < len; a = a + 1 ){ 694 char c = client.read(); 695 output = output + c; 696 } 697 return output; 698} 699 700int web_search(String parameter){ 701 send_byte(0x40); // Host FS reponse start 702 String match; 703 int start; 704 int end; 705 int num = 0; 706 707 http_get(webhost, "/?search=" + parameter, 0, 0); 708 send_string(String(TT_RED)); 709 send_string("Webhost: "+webhost+"\ \ 710"); 711 while (client.available() && num < MAX_WEBLINKS){ 712 String line = client.readStringUntil(0xA); 713 //Serial.println(line); 714 start = line.indexOf("discs/"); 715 if (start != -1) { 716 end = line.indexOf(".ssd",start+6); 717 weblink[num] = line.substring(start+6,end); 718 start = weblink[num].indexOf("-"); 719 match = String(TT_CYAN) + num; 720 match = match + String(TT_WHITE) + "-" + String(TT_YELLOW) + weblink[num].substring(start+1) + "\ \ 721"; 722 send_string(match); // Host FS send string 723 num++; 724 } 725 } 726 weblinks = num; 727 send_byte(0x00); // Host FS reponse end 728} 729 730int web_mount(){ 731 Serial.println("\ 732Reading "+webhost+webdisc); 733 if (http_get(webhost, webdisc, 0, CAT_LENGTH-1)) {Serial.println("*** HTTP GET ERROR ***");return -1;}; 734 uint8_t buffer [CAT_LENGTH]; 735 for( int a = 0; a < CAT_LENGTH; a = a + 1 ){ 736 char c = client_read(); 737 buffer[a] = c; 738 } 739 memcpy(&disc,buffer,CAT_LENGTH); 740 client_empty(); 741 Serial.println("*** MOUNTED WEB SSD OK ***"); 742 return 0; 743} 744 745// BBC LOAD DATA 746void web_load(int fileID) { 747 Serial.print("\ 748\ FileID "); 749 Serial.print(fileID,DEC); 750 Serial.print("\ 751\ "); 752 //Serial.print(disc.filename[fileID]); TODO #DEFINE for disc filename 753 Serial.print(" "); 754 Serial.print(disc.fileinfo[fileID].loadAddr, HEX); 755 Serial.print(" "); 756 Serial.print(disc.fileinfo[fileID].execAddr, HEX); 757 Serial.print(" "); 758 Serial.print(disc.fileinfo[fileID].fileLen, HEX); 759 Serial.print(" "); 760 Serial.print(disc.fileinfo[fileID].startSector, HEX); 761 Serial.println(" "); 762 http_get(webhost, webdisc, disc.fileinfo[fileID].startSector*0x100, disc.fileinfo[fileID].fileLen+disc.fileinfo[fileID].startSector*0x100); 763 while (!client.available()){}; // wait for data to be returned 764 for( int a = 0; a < disc.fileinfo[fileID].fileLen; a = a + 1 ){ 765 char c = client_read(); 766 send_byte(c); 767 //Serial.println(a,HEX); 768 // Serial.print(" "); 769 // Serial.println(c,HEX); 770 } 771 client_empty(); 772} 773 774// ========================================================================== 775// SECTION: MAIN 776// ========================================================================== 777 778void setup() { 779 setup_serial(); 780 #ifdef UPURSFSv008 781 setup_wifi(); 782 #endif 783} 784 785void loop() { 786 // Check for commands from BBC Micro 787 if (SerialBBC.available () && (raw_read() == ESCAPE)) { 788 int nextByte = raw_read(); 789 if (nextByte != ESCAPE) {runCommand (nextByte);}; // run command if it's not an escaped escape 790 } 791} 792
ArduinoHost.ino
arduino
Enables a BBC Micro to view an Arduino as a file system host over a serial connection. Arduino connects to wifi, enabling the BBC Micro to mount a .ssd DFS disc image over the Internet.
1// Arduino Host - 8bitkick 2// ------------------------ 3// 4// Storage medium is BBC Micro DFS disc images (.ssd) served from bbcmicro.co.uk 5// 6// 7// Reference 8// 9// HostFS - Sweh 10// github.com/sweharris/TubeHost/blob/master/TubeHost 11// 12// Arduino-UPURS I/O - Myelin 13// github.com/google/myelin-acorn-electron-hardware/blob/master/upurs_usb_port/upurs_usb_port.ino 14// 15 16 17#include <Arduino.h> 18#include "wiring_private.h" 19#include <SPI.h> 20#include <WiFi101.h> 21#include <FlashStorage.h> 22 23#define UPURSFSv008 // Boot behavior differs from v0.03 24 25#define DEBUG 26 27 28// WIFI & WEB 29 30char ssid[] = SECRET_SSID; 31char pass[] = SECRET_PASS; 32int status = WL_IDLE_STATUS; 33 34WiFiClient client; 35 36#define TCPPORT 80 37#define MAX_WEBLINKS 20 38 39String webhost = "www.bbcmicro.co.uk"; 40String webdisc = "/gameimg/discs/79/Disc005-DareDevilDenis.ssd"; // This is effectively our current URL 41String weblink[MAX_WEBLINKS]; // Here we cache hyperlinks from searching the given webhost 42int weblinks = 0; 43 44 45// SERIAL WIRE DEFINES 46 47#define USE_ARDUINO_MKR // We assume RX & TX signals are inverted externally as we can't use SofwareSerial. 48// NB Must use 5v <> 3.3v level shifter with Arduino MKR 49 50#define TXD_PIN 0 // Serial transmit 51#define RXD_PIN 1 // Serial receive 52 53#define RTS_PIN 3 // active-high output Requesting them To Send. We are always ready so this stays high 54#define CTS_PIN 4 // active-high input tells us we're Cleared To Send. (Using 4 as we can attach interrupt) 55 56#define LED_PIN 6 57 58 59// TUBE HOST COMMAND DEFINES 60// mdfs.net/Software/Tube/Serial/Protocol 61 62#define ESCAPE 0x7F 63#define OSRDCHIO 0x00 // Cy A 64#define OSCLI 0x02 // string 0x0D 0x7F or 0x80 65#define OSBYTELO 0x04 // X A X 66#define OSBYTEHI 0x06 // X Y A Cy Y X 67#define OSWORD 0x08 // A in_length block out_length block 68#define OSWORD0 0x0A // block 0xFF or 0x7F string 0x0D 69#define OSARGS 0x0C // Y block A A block 70#define OSBGET 0x0E // Y Cy A 71#define OSBPUT 0x10 // Y A 0x7F 72#define OSFIND 0x12 // 0x00 Y 0x7F 73#define OSFIND 0x12 // A string 0x0D A 74#define OSFILE 0x14 // block string 0x0D A A block 75#define OSGBPB 0x16 // block A block Cy A 76#define OSFSC 0x18 // X Y A 0xFF Y X or 0x7F 77 78int long timer = 0; 79 80// DISC 81// http://beebwiki.mdfs.net/Acorn_DFS_disc_format 82 83 84#define CAT_LENGTH 512 85 86// Boot option :- 87// Bit 5 Bit 4 Action 88// 0 0 No action 89// 0 1 *LOAD $.!BOOT 90// 1 0 *RUN $.!BOOT 91// 1 1 *EXEC $.!BOOT 92 93struct DFS_Disc 94{ 95 // DFS catalogue information 512 bytes (Sectors 0 & 1) 96 char volumeLo[8]; 97 char filename[31][8]; 98 99 char volumeHi[4]; 100 uint8_t cycle; 101 uint8_t numberFiles; 102 uint8_t bootOption; 103 uint8_t sectors; 104 105 struct Fileinfo{ 106 uint16_t loadAddr; 107 uint16_t execAddr; 108 uint16_t fileLen; 109 uint8_t bits; 110 uint8_t startSector; 111 }fileinfo[31]; 112}; 113 114DFS_Disc disc; 115 116 117/* TODO - implement defines for bitfield derived DFS values 118// from DFS.c - Dominic Beesley 119#define DFS_SECTORCOUNT (unsigned int)((dfs->header.sector1.seccount_l \\ 120+ ((dfs->header.sector1.opt4 & 3) << 8)) ) 121 122#define DFS_CATCOUNT(dfs) (dfs->header.sector1.cat_count >> 3) 123 124#define DFS_FILE_LOCKED(dfs, index) \\ 125( (dfs->header.sector0.file_names[index].dir & 0x80) != 0) 126 127#define DFS_EX(x) ( ((x & 0x30000) == 0x30000)? x | 0xFC0000:x) 128 129#define DFS_FILE_LOAD(dfs, index) DFS_EX(( dfs->header.sector1.file_addrs[index].load_l \\ 130+ (dfs->header.sector1.file_addrs[index].load_h << 8) \\ 131+ ( (unsigned long int) (dfs->header.sector1.file_addrs[index].morebits & 0x0C) << 14) )) 132 133#define DFS_FILE_LEN(dfs, index) ( dfs->header.sector1.file_addrs[index].len_l \\ 134+ (dfs->header.sector1.file_addrs[index].len_h << 8) \\ 135+ ( (unsigned long int) (dfs->header.sector1.file_addrs[index].morebits & 0x30) << 12) ) 136 137#define DFS_FILE_EXEC(dfs, index) DFS_EX(( dfs->header.sector1.file_addrs[index].exec_l \\ 138+ (dfs->header.sector1.file_addrs[index].exec_h << 8) \\ 139+ ( ((unsigned long int) dfs->header.sector1.file_addrs[index].morebits & 0xC0) << 10) )) 140 141#define DFS_FILE_SECTOR(dfs, index) ( dfs->header.sector1.file_addrs[index].secaddr_l \\ 142+ ( (dfs->header.sector1.file_addrs[index].morebits & 0x03 ) << 8) ) 143*/ 144 145 146// Teletext colour codes 147#define TT_RED char(129) 148#define TT_GREEN char(130) 149#define TT_YELLOW char(131) 150#define TT_BLUE char(132) 151#define TT_MAGENTA char(133) 152#define TT_CYAN char(134) 153#define TT_WHITE char(135) 154 155 156// ========================================================================== 157// SECTION: Arduino Serial port 158// ========================================================================== 159 160 161// Fast GPIO access for MKR1000 162#ifdef USE_ARDUINO_MKR 163 164inline void digitalWrite_fast(int pin, bool val) { if (val) 165 PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg = (1ul << g_APinDescription[pin].ulPin); 166 else 167 PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg = (1ul << g_APinDescription[pin].ulPin); 168} 169 170inline int digitalRead_fast(int pin) { 171 return !!(PORT->Group[g_APinDescription[pin].ulPort].IN.reg & (1ul << g_APinDescription[pin].ulPin)); 172} 173 174#define CTS_IS_ACTIVE (digitalRead_fast(CTS_PIN) == HIGH) 175 176// Using SAMD21 SERCOM for UART instances 177Uart SerialBBC (&sercom3, RXD_PIN, TXD_PIN, SERCOM_RX_PAD_1, UART_TX_PAD_0); // Create the new UART instance assigning it to pin 0 and 1 178void SERCOM3_Handler() { 179 SerialBBC.IrqHandler(); 180} 181#endif USE_ARDUINO_MKR 182 183 184// Keep track of CTS timing with this interrupt handler (micros() OK if we're quick about it....) 185volatile unsigned long CTS_since = 0; 186void CTSchanged() {CTS_since = micros();} 187 188void setup_serial() { 189 190 // Using SAMD21 SERCOM for UART instances 191 #ifdef USE_ARDUINO_MKR 192 pinPeripheral(RXD_PIN, PIO_SERCOM); //Assign RX function to pin 0 193 pinPeripheral(TXD_PIN, PIO_SERCOM); //Assign TX function to pin 1 194 #endif USE_ARDUINO_MKR 195 196 // SET UP SERIAL 197 Serial.begin(115200); 198 SerialBBC.begin(115200); 199 200 pinMode(RTS_PIN, OUTPUT); 201 digitalWrite(RTS_PIN, HIGH); 202 pinMode(CTS_PIN, INPUT); 203 attachInterrupt(digitalPinToInterrupt(CTS_PIN), CTSchanged, RISING); 204} 205 206 207// Implement hardware flow control by monitoring CTS 208void raw_write(byte byteOut) { 209 while ( 210 !( 211 CTS_IS_ACTIVE && // We must be clear to send 212 (micros() - CTS_since > 3) && // for at least 3us 213 (micros() - CTS_since < 20) // and last successful byte transfer <20us ago 214 ) 215 ){}; // Otherwise wait for this condition 216 SerialBBC.write(byteOut); 217 218 if (CTS_IS_ACTIVE) { 219 CTS_since = micros()+3; 220 } 221} 222 223int long raw_read() {while (SerialBBC.available() == 0){};return SerialBBC.read();} 224 225 226// ========================================================================== 227// SECTION: Serial I/O routines for communication with Client 228// ========================================================================== 229 230 231int long read_addr() {uint32_t addr = 0;for( int a = 0; a < 4; a++ ){addr = addr * 256 + raw_read();}return addr;} 232 233void send_byte(byte byteOut) {raw_write(byteOut);if (byteOut==ESCAPE) {raw_write(ESCAPE);};} 234 235void send_cmd(byte byteOut) {raw_write(ESCAPE);raw_write(byteOut);} 236 237void send_addr(uint32_t addr) {send_byte(addr >> 24);send_byte((addr >> 16) & 0xFF);send_byte((addr >> 8) & 0xFF);send_byte(addr & 0xFF);} 238 239void send_data(uint8_t *data, int length) { 240 for(int n=0; n < length; n++) { 241 raw_write(data[n]); 242 } 243} 244 245void send_string(String message) { 246 unsigned int len = message.length(); 247 for(int n=0; n < len; n++) { 248 send_byte(message[n]); 249 } 250}; 251 252void send_reply(String message) { 253 unsigned int len = message.length(); 254 for(int n=0; n < len; n++) { 255 send_byte(message[n]); 256 } 257 send_byte(0x00); 258}; 259 260void send_output(String message) { 261 send_byte(0x40); 262 send_reply(message); 263}; 264 265String read_string() { 266 byte nextByte = 0; int index = 0; char stringbuff[80]; 267 while (nextByte != 0x0D && index<80){ 268 if (nextByte != 0) {stringbuff[index++] = nextByte; 269 } 270 nextByte = raw_read(); 271 } 272 stringbuff[index] = 0; 273 274 return stringbuff; 275} 276 277String extract_filename(String data) 278{ 279 String filename; 280 int i = 0; 281 282 while (data[i] != 0x20 && data[i] != 0x00 && i < data.length()) { 283 if (data[i] != 34) {filename = filename + data[i];}; // REMOVE " FROM FILENAME 284 i++; 285 } 286 return filename; 287} 288 289String extract_parameter(String data) 290{ 291 String ret; 292 int i = 0; 293 294 while (data[i] != 0x20 && data[i] != 0x00 && i < data.length()) { 295 i++; 296 } 297 i++; 298 while (data[i] != 0x20 && data[i] != 0x00 && i < data.length()) { 299 if (data[i] != 34) {ret = ret + data[i];}; // REMOVE " FROM FILENAME 300 i++; 301 } 302 return ret; 303} 304 305void error(uint8_t code, const char* message) { 306 send_cmd(0); 307 send_byte(code); 308 send_reply(message); 309} 310 311void send_RLE_image(uint32_t loadAddr, uint8_t *data, int length) { 312 send_cmd(0xE0); // Load data response 313 send_addr(loadAddr); // Address to load to 314 int bytes = 0; 315 for(int n=0; n < length; n=n+2) { // Data 316 int value = data[n]; 317 int runlength = data[n+1]; 318 for(int x=0; x < runlength; x++) { 319 bytes++; 320 send_byte(value); 321 } 322 } 323 send_cmd(0xB0); // End 324} 325 326// BBC LOAD DATA 327void send_data_to_memory(uint32_t loadAddr, uint8_t *data, int length) { 328 send_cmd(0xE0); // Load data response 329 send_addr(loadAddr); // Address to load to 330 send_data(data, length); // Data 331 send_cmd(0xB0); // End 332} 333 334// BBC SAVE DATA 335void read_data_from_memory(uint32_t saveAddr, int length) { 336 send_cmd(0xF0); 337 send_addr(saveAddr); 338 for(int n=0; n < length; n=n+1) { 339 const byte byteIn = raw_read(); 340 Serial.write(byteIn); 341 } 342 send_cmd(0xB0); 343 Serial.print(length,DEC); 344 Serial.println(" bytes saved\ 345"); 346} 347 348// ========================================================================== 349// SECTION: OSFSC 350// ========================================================================== 351 352 353void runOSFSC() { 354 byte X = raw_read(); byte Y = raw_read(); byte A = raw_read(); 355 char fname [10]; 356 switch (A) { 357 /* 358 case 0: // OPT Handler 359 case 1: // Check EOF 360 case 2: // *Handler 361 case 6: // Shutdown files 362 case 7: // Give file handle ranges 363 case 8: // *ENABLE checker */ 364 case 3: // Unrecognised * command handler 365 OSFSC_cmd(X,Y); 366 break; 367 // *RUN handler 368 case 4: { 369 send_byte(127); // Request filename 370 String filename = extract_filename(read_string()); 371 int fileID = DFS_find(filename); 372 if (fileID != -1) { 373 send_cmd(0xE0); // Start data send 374 send_addr(disc.fileinfo[fileID].loadAddr); // Address to load to 375 web_load(fileID); // Send file data 376 send_cmd(0xB0); // End 377 send_cmd(0xc0); // set execution address 378 send_addr(disc.fileinfo[fileID].execAddr); 379 send_byte(0x80); // do it! 380 } else { 381 error(214,"file not found"); 382 } 383 } 384 break; 385 // *CAT handler 386 case 5: { 387 String response = DFS_cat(); 388 send_byte(127); 389 send_byte(0x40); 390 send_string(response); 391 send_byte(0); 392 } 393 break; 394 // BOOT (See https://github.com/sweharris/TubeHost ) 395 case 255: 396 send_byte(255); send_byte(0);send_byte(X); 397 setup_wifi(); // We restart wifi on boot 398 // web_mount(); // DEBUG 399 break; 400 default: 401 error(214,"ArduinoHost: unhandled OSFSC"); 402 Serial.print("Unhandled OSFSC A=0x"); 403 Serial.print(A, HEX); 404 break; 405 } // end of switch 406} 407 408// Handle custom * commands here 409// ----------------------------- 410void OSFSC_cmd (uint8_t X, uint8_t Y){ 411 send_byte(ESCAPE); // tell client to send us the command 412 String commandline = read_string(); 413 String command = extract_filename(commandline); 414 String parameter = extract_parameter(commandline); 415 416 if (command=="WIFI") { 417 setup_wifi(); 418 if (web_mount()==0) { 419 send_byte(0x40); // Reponse start 420 send_wifi_status(); 421 send_byte(0x00); // Reponse end 422 return; 423 } 424 } 425 426 if (command=="SEARCH") { // Search host 427 web_search(parameter); 428 return; 429 } 430 /* 431 if (command=="HOST") { // Change / display host 432 if (parameter!=""){ 433 webhost=parameter; 434 } 435 send_byte(0x40); // Host FS reponse start 436 send_string(String(TT_RED)+webhost+"\ \ 437"); 438 send_byte(0x00); // Host FS reponse end 439 440 return; 441 }*/ 442 443 if (command=="MOUNT") { 444 webdisc = "/gameimg/discs/"+weblink[parameter.toInt()]+".ssd"; // TODO - catch dsd 445 web_mount(); // TODO catch error 446 String cat = DFS_cat(); 447 send_byte(0x40); // Host FS reponse start 448 send_string(String(TT_YELLOW)+webdisc+"\ \ 449"+cat); // Host FS send string 450 send_byte(0x00); // Host FS reponse end 451 452 return; 453 } 454 455 error(214,"ArduinoHost: unhandled * command"); 456} 457// ========================================================================== 458// SECTION: OSFILE 459// ========================================================================== 460 461void runOSFILE() { 462 uint32_t end = read_addr(); 463 uint32_t save = read_addr(); 464 uint32_t exec = read_addr(); 465 uint32_t load = read_addr(); 466 String filename = extract_filename(read_string()); 467 byte A = raw_read(); 468 switch (A) { 469 case 0: // SAVE DATA 470 timer = micros(); 471 read_data_from_memory(save, end-save); 472 timer = micros() - timer; 473 Serial.println("*SAVE " + filename); 474 Serial.print(timer, DEC); 475 Serial.println("us "); 476 break; 477 case 255: { // LOAD DATA 478 int fileID = DFS_find(filename); 479 if (fileID != -1){ 480 timer = micros(); 481 if (load==0) {load = disc.fileinfo[fileID].loadAddr;}; 482 send_cmd(0xE0); // Start load data 483 send_addr(load); // Address to load to 484 web_load(fileID);// Load data 485 send_cmd(0xB0); // End 486 timer = micros() - timer; 487 Serial.println("*LOAD " + filename); 488 Serial.print(timer, DEC); 489 Serial.println("us "); 490 } else { 491 error(214,"file not found"); 492 } 493 break; 494 } 495 default: 496 error(214,"Unhandled OSFILE"); 497 Serial.print("Unhandled OSFILE A=0x"); 498 Serial.print(A, HEX); 499 break; 500 } // end of switch 501 // Send results 502 send_byte(1); // File found; directories aren't possible 503 send_addr(end); 504 send_addr(save); 505 send_addr(exec); 506 send_addr(load); 507} 508 509// Replace with jump table 510void runCommand (const byte inByte) { 511 switch (inByte) { 512 case OSFSC: runOSFSC(); 513 break; 514 case OSFILE: runOSFILE(); 515 break; 516 default: { 517 Serial.print("Unhandled command 0x"); 518 Serial.print(inByte, HEX); 519 } 520 break; 521 } // end of switch 522} 523 524// ========================================================================== 525// SECTION: Wifi & http get 526// ========================================================================== 527 528String send_wifi_status() { 529 if (status != WL_CONNECTED) { 530 // Connect to WPA/WPA2 network. Change this line if using open or WEP network: 531 status = WiFi.begin(ssid, pass); 532 send_string("Connected to wifi\ \ 533"); 534 } else 535 { 536 uint32_t myAddr = WiFi.localIP(); 537 String first_octet = String((myAddr >> 24) & 0xFF); 538 byte second_octet = (myAddr >> 16) & 0xFF; 539 byte third_octet = (myAddr >> 8) & 0xFF; 540 byte fourth_octet = myAddr & 0xFF; 541 String localIP = first_octet + "." + second_octet + "." + third_octet + "." + fourth_octet; 542 send_string( "Wifi connected"); 543 send_string("\ \ 544Local IP: "); 545 send_string(localIP); 546 send_string("\ \ 547Webhost : "+webhost); 548 send_string("\ \ 549"); 550 } 551} 552 553void setup_wifi(){ 554 // Edit this section for static IP, remove for DHCP 555 IPAddress ip(172, 20, 20, 61); 556 IPAddress subnet(255, 255, 255, 0); 557 IPAddress dns(75, 75, 75, 75); 558 IPAddress gateway(172, 20, 20, 1); 559 WiFi.config(ip, dns, gateway, subnet); 560 // check for the presence of the shield 561 if (WiFi.status() == WL_NO_SHIELD) { 562 Serial.println("WiFi shield not present"); 563 // don't continue: 564 while (true); 565 } 566 567 // attempt to connect to Wifi network: 568 while (status != WL_CONNECTED) { 569 Serial.print("Attempting to connect to SSID: "); 570 Serial.println(ssid); 571 // Connect to WPA/WPA2 network. Change this line if using open or WEP network: 572 status = WiFi.begin(ssid, pass); 573 // wait 1 seconds for connection: 574 delay(3000); 575 } 576 Serial.println("Connected to wifi"); 577} 578 579// GET 580int http_get(String host, String path, int start, int end) { 581 client.stop(); 582 String range = "Range: bytes="; // NB - WE REQUIRE WEBSERVER TO SUPPORT PARTIAL CONTENT 583 if (start != 0) {range = range + start;} else {range = range + "0";} 584 range = range + "-"; 585 range = range + end; 586 Serial.println(range); 587 // if there's a successful connection: 588 if (client.connect(host.c_str(), TCPPORT)) { 589 client.println("GET " + path + " HTTP/1.1"); 590 client.println("Host: "+ host); 591 if (start != end) {client.println(range);} // Skip range request if start=end 592 client.println("Connection: close"); 593 client.println(); 594 // Check response 595 char status[32] = {0}; 596 client.readBytesUntil('\r', status, sizeof(status)); 597 if (strcmp(status, "HTTP/1.1 206 Partial Content") != 0) { 598 Serial.print(F("Unexpected response: ")); 599 Serial.println(status); 600 return 1; 601 } 602 char endOfHeaders[] = "\ \ 603\ \ 604"; 605 if (!client.find(endOfHeaders)) { 606 Serial.println(F("Invalid response")); 607 return 1; 608 } 609 return 0; 610 } 611} 612 613char client_read(){ 614 while (!client.available()){}; 615 return client.read(); 616}; 617 618char client_empty(){ 619 while (client.available()){ 620 Serial.write(client.read()); 621 } // TODO test for unused data recieved 622} 623 624// ========================================================================== 625// SECTION: HOST MEDIUM - WEB-BASED BBC MICRO DFS DISC IMAGES (.ssd) 626// ========================================================================== 627// Take filename and return filehandle (relative pointer to DFS catalogue entry) 628 629String DFS_filename(int fileID) { 630 char filename[10]; 631 filename[0] = disc.filename[fileID][7] & 0x7F; 632 filename[1] = '.'; 633 memcpy(filename+2, disc.filename[fileID], 7); 634 for (int i=0; i<9; i++) { 635 if (filename[i] <= 32) 636 filename[i] = 0; 637 } 638 filename[9] = 0; 639 return String(filename); 640} 641 642String DFS_volume() { 643 char volume[13]; 644 memcpy(volume, disc.volumeLo, 8); 645 memcpy(volume+8, disc.volumeHi, 4); 646 for (int i=0; i<12; i++) { 647 if (volume[i] <= 32) 648 volume[i] = 0; 649 } 650 volume[12] = 0; 651 return String(volume); 652} 653 654 655int DFS_find(String filename){ 656 int id = -1; 657 if (filename.indexOf('.') != 1) {filename = "$."+filename;} 658 for( int a = 0; a < (disc.numberFiles >> 3); a = a + 1 ){ 659 String thisfile = DFS_filename(a); 660 if (filename.equalsIgnoreCase(thisfile)) {id = a;} 661 } 662 return id; 663} 664 665String DFS_cat(){ 666 // Volume title 667 String cat; 668 uint8_t files = disc.numberFiles >> 3; // number of files 669 cat = " "+String(TT_YELLOW) + "\ \ 670Volume title: "+DFS_volume()+" \ \ 671"; 672 673 for( int a = 0; a < files; a = a + 1 ){ 674 675 cat = cat + DFS_filename(a); 676 cat = cat + " "; 677 cat = cat + String(disc.fileinfo[a].loadAddr, HEX); 678 cat = cat + " "; 679 cat = cat + String(disc.fileinfo[a].execAddr, HEX); 680 cat = cat + " "; 681 cat = cat + String(disc.fileinfo[a].fileLen, HEX); 682 cat = cat + " "; 683 cat = cat + String(disc.fileinfo[a].startSector, HEX); 684 cat = cat + "\ \ 685"; 686 } 687 Serial.println(cat); 688 return cat; 689} 690 691String get_string(int len){ 692 String output = ""; 693 for( int a = 0; a < len; a = a + 1 ){ 694 char c = client.read(); 695 output = output + c; 696 } 697 return output; 698} 699 700int web_search(String parameter){ 701 send_byte(0x40); // Host FS reponse start 702 String match; 703 int start; 704 int end; 705 int num = 0; 706 707 http_get(webhost, "/?search=" + parameter, 0, 0); 708 send_string(String(TT_RED)); 709 send_string("Webhost: "+webhost+"\ \ 710"); 711 while (client.available() && num < MAX_WEBLINKS){ 712 String line = client.readStringUntil(0xA); 713 //Serial.println(line); 714 start = line.indexOf("discs/"); 715 if (start != -1) { 716 end = line.indexOf(".ssd",start+6); 717 weblink[num] = line.substring(start+6,end); 718 start = weblink[num].indexOf("-"); 719 match = String(TT_CYAN) + num; 720 match = match + String(TT_WHITE) + "-" + String(TT_YELLOW) + weblink[num].substring(start+1) + "\ \ 721"; 722 send_string(match); // Host FS send string 723 num++; 724 } 725 } 726 weblinks = num; 727 send_byte(0x00); // Host FS reponse end 728} 729 730int web_mount(){ 731 Serial.println("\ 732Reading "+webhost+webdisc); 733 if (http_get(webhost, webdisc, 0, CAT_LENGTH-1)) {Serial.println("*** HTTP GET ERROR ***");return -1;}; 734 uint8_t buffer [CAT_LENGTH]; 735 for( int a = 0; a < CAT_LENGTH; a = a + 1 ){ 736 char c = client_read(); 737 buffer[a] = c; 738 } 739 memcpy(&disc,buffer,CAT_LENGTH); 740 client_empty(); 741 Serial.println("*** MOUNTED WEB SSD OK ***"); 742 return 0; 743} 744 745// BBC LOAD DATA 746void web_load(int fileID) { 747 Serial.print("\ 748\ FileID "); 749 Serial.print(fileID,DEC); 750 Serial.print("\ 751\ "); 752 //Serial.print(disc.filename[fileID]); TODO #DEFINE for disc filename 753 Serial.print(" "); 754 Serial.print(disc.fileinfo[fileID].loadAddr, HEX); 755 Serial.print(" "); 756 Serial.print(disc.fileinfo[fileID].execAddr, HEX); 757 Serial.print(" "); 758 Serial.print(disc.fileinfo[fileID].fileLen, HEX); 759 Serial.print(" "); 760 Serial.print(disc.fileinfo[fileID].startSector, HEX); 761 Serial.println(" "); 762 http_get(webhost, webdisc, disc.fileinfo[fileID].startSector*0x100, disc.fileinfo[fileID].fileLen+disc.fileinfo[fileID].startSector*0x100); 763 while (!client.available()){}; // wait for data to be returned 764 for( int a = 0; a < disc.fileinfo[fileID].fileLen; a = a + 1 ){ 765 char c = client_read(); 766 send_byte(c); 767 //Serial.println(a,HEX); 768 // Serial.print(" "); 769 // Serial.println(c,HEX); 770 } 771 client_empty(); 772} 773 774// ========================================================================== 775// SECTION: MAIN 776// ========================================================================== 777 778void setup() { 779 setup_serial(); 780 #ifdef UPURSFSv008 781 setup_wifi(); 782 #endif 783} 784 785void loop() { 786 // Check for commands from BBC Micro 787 if (SerialBBC.available () && (raw_read() == ESCAPE)) { 788 int nextByte = raw_read(); 789 if (nextByte != ESCAPE) {runCommand (nextByte);}; // run command if it's not an escaped escape 790 } 791} 792
ArduinoHost
Enables a BBC Micro to view an Arduino as a file system host over a serial connection. Arduino connects to wifi, enabling the BBC Micro to mount a .ssd DFS disc image over the Internet.
ArduinoHost
Enables a BBC Micro to view an Arduino as a file system host over a serial connection. Arduino connects to wifi, enabling the BBC Micro to mount a .ssd DFS disc image over the Internet.
Downloadable files
MKR level-shifting / inverting shield
After breadboarding for a while I made a shield that I could fit more easily inside the case of the computer.
MKR level-shifting / inverting shield
BBC User Port - UPURS
Connection into the BBC Micro User port (viewed with BBC upside down)
BBC User Port - UPURS
BBC User Port - UPURS
Connection into the BBC Micro User port (viewed with BBC upside down)
BBC User Port - UPURS
BBC User Port - UPURS
Connection into the BBC Micro User port (viewed with BBC upside down)
BBC User Port - UPURS
Breadboard
This does the level shifting of all signals and inverts RX and TX
Breadboard
MKR level-shifting / inverting shield
After breadboarding for a while I made a shield that I could fit more easily inside the case of the computer.
MKR level-shifting / inverting shield
BBC User Port - UPURS
Connection into the BBC Micro User port (viewed with BBC upside down)
BBC User Port - UPURS
Comments
Only logged in users can leave comments
8bitkick
0 Followers
•0 Projects
0