├── .gitignore ├── LICENSE ├── README.md ├── assets ├── circuit1.jpeg ├── cpc_copy.jpeg ├── cpc_dir.jpeg └── schematic.svg ├── compile_sketch.sh ├── cpc6128_interface.ino ├── cpc_files ├── COPY.BAS ├── DIR.BAS ├── copy.asm ├── copy.rom ├── dir.asm └── dir.rom ├── monitor.sh └── upload_sketch.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.lst 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bruno Conde 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interface Amstrad CPC 6128 with an Arduino 2 | 3 | The goal of this project is to interface with the Amstrad CPC 6128 expansion port using a Microcontroller (Arduino Mega) to transfer information (games :-)) between the CPC and a micro SD card. 4 | 5 |
6 | Breadboard circuit 7 |
8 | 9 | ## Circuit and decoding logic 10 | 11 | The Arduino Mega communicates with the CPC like any other peripheral device, using the IO port. In this case, we use port &FBD0 as its typically used for Serial communication: [http://cpctech.cpc-live.com/docs/iopord.html](http://cpctech.cpc-live.com/docs/iopord.html). 12 | 13 | We can transfer bytes to/from peripheral devices using the **IN** and **OUT** Z80 CPU instructions: 14 | 15 | OUT &FBD0, value // send byte 16 | value = INP(&FBD0) // receive byte 17 | 18 | The decoding logic was implemented using just a couple of NOR gates and one AND gate with the relevant address and control lines. If bit 10 and bit 5 are reset, this means we are using an expansion peripheral and, more specifically, the serial port according to the CPC I/O port allocation. The other bits are ignored. Therefore, the decoding logic uses only these address lines. 19 | 20 | The (**D0…D7**) are the data lines. These lines will contain the byte being transferred. 21 | 22 | When there's an I/O request, the Z80 brings the **IOREQ** line low. The IN and OUT operations are identified by the **RD** and **WR** lines, respectively. When the CPU reads a given port with IN, the **RD** line is LOW; otherwise, it is high. 23 | 24 | Another important signal is the **M1** which stands for Machine cycle one. Each instruction cycle is composed of tree machine cycles: M1, M2 and M3. M1 is the "op code fetch" machine cycle. This signal is active low. We must make sure M1 is high when communicating with the Z80. 25 | 26 | The final signal (and definitely the most interesting) is the **WAIT**. When this signal is low, it tells the CPU that the addressed memory or **I/O devices** are not ready for a data transfer. The CPU will continue to enter the WAIT state whenever this signal is active, effectively pausing the CPU. 27 | 28 | While assembling the circuit, I discovered that some of the CPC 6128 lines required pull-up resistors. The interrupt line was being triggered without any reason because these pins were floating, namely the address lines A0, A5, A10, and the IOREQ line. I suspect this is related to the chip family I used: 74HC. Other similar projects (see references) used CMOS chips and didn't need any pull-up resistors. 29 | 30 | Circuit components: 31 | - 220Ω resistor x 3 32 | - 10kΩ resistor x 1 33 | - NPN transistor x 1 34 | - 74HC21N x 1 35 | - 74HC27N x 1 36 | - Arduino Mega 2560 x 1 37 | - Breadboard x 1 38 | - Micro SD card reader x 1 39 | 40 | 41 | ## Synchronizing the Z80 with the arduino 42 | 43 | Timing when communicating between Z80 IN/OUT instructions and the Arduino is critical. The Z80 is clocked at 4 MHz while the Arduino Mega (which I'm using for this project) is clocked at 16 MHz. However, this speed difference is not sufficient for the Arduino to reply to the Z80 in time or read the data bus before the Z80 moved on to do other things and released it. Hence, we must use the WAIT signal to pause the CPU while the Arduino does its job of a) putting a byte into the data bus or b) reading a byte from the data bus. 44 | 45 | Whenever the decoding logic signals that a byte is being transferred (IN/OUT), an interrupt is triggered in the Arduino. We can then set the WAIT line LOW. Again, timing is the key. Setting the WAIT line LOW using software only after the interrupt is triggered is not an option because the Z80 WAIT state is sampled before we can reply. 46 | 47 | Therefore, the interrupt signal itself is used to bring the WAIT line low. After this, we must find a way to release the WAIT line (set it HIGH) after the Arduino finishes processing the byte. This can be done using a transistor and a control line from the Arduino as a switch. The control line is connected to the Emitter, the interrupt line connected to the Base, and the WAIT line to the Collector. 48 | 49 | This control line will always be active. This means the WAIT signal is also triggered if this control line is LOW and the interrupt is also triggered. When the Arduino is ready, it will bring the control line HIGH for a brief moment, giving enough time for the Z80 to process the byte (in case of an IN instruction). 50 | 51 | This *moment* is also crucial. If it's too long, the Arduino might not be ready to process the next interrupt. On the other hand, if it is too short, the Z80 might not have enough time to sample the data bus. 52 | 53 | Studying the Z80 timing diagram for Input/Output cycles, we can see that the **In** is sampled from the data bus for a brief moment, and right after this, the IOREQ goes HIGH. 54 | 55 | I used this knowledge to release the Arduino line at just the right time. If the IOREQ line is HIGH, this means the interrupt line is no longer active. Right after pulling the control line HIGH, the interrupt line is polled continuously. When this signal changes, we can bring the control line LOW again to be ready for the next request/interrupt. Here is where we take advantage of the faster clock on the Arduino. Still, this poll needs to be done in AVR assembly to ensure the Arduino starts polling the line before the Z80 sets the IOREQ HIGH. Here is the code that releases the WAIT line: 56 | 57 | void releaseWaitAfterWrite() { 58 | __asm__ __volatile__( 59 | "SBI PORTC, 0 \n" // Set bit 0 in PORTC - WAIT line HIGH 60 | "SBIC PINE, 4 \n" // Skip next instruction if Bit 4 (Interrupt) is Cleared 61 | "RJMP .-4 \n" // Relative Jump -4 bytes - 62 | "LDI r25, 0x00 \n" // Load r25 with 0x00 - B00000000 63 | "OUT DDRA, r25 \n" // store r25 in DDRA - Set DDRA as output again (default) 64 | "CBI PORTC, 0 \n" // Clear bit 0 in PORTC - WAIT line LOW 65 | "SEI \n" // Set Global Interrupt 66 | 67 | :::"r25" 68 | ); 69 | } 70 | 71 | 72 | ## Listing and copying files 73 | 74 | Once we are capable of transferring bytes to and from the Arduino, we can do just about anything. I created a simple protocol to communicate with the Arduino using IN/OUT instructions. Using this protocol, I programmed two small Z80 assembly programs: 75 | - *dir* – lists all the files present on the root folder of the SD card 76 | - *copy* – which, provided with the filename as a parameter, copies the SD card file into the CPC disk drive. 77 | 78 | Most games nowadays are compacted into ".DSK" files, a disk image format. The files must be extracted and placed on the SD card root. 79 | 80 | Here are a couple of screenshots of these programs: 81 | 82 |
83 | dir cmd 84 |
85 | 86 |
87 | copy cmd 88 |
89 | 90 | ## Source code 91 | 92 | This repository contains the following source files: 93 | * [cpc6128_interface.ino](/cpc6128_interface.ino) - C++ sketch responsible for communicating with the CPC and reading the micro SD card. 94 | * [dir.asm](/cpc_files/dir.asm) - Z80 assembly program to catalog the files on the SD card 95 | * [DIR.BAS](/cpc_files/DIR.BAS) - "dir" Basic entry program 96 | * [copy.asm](/cpc_files/copy.asm) - Z80 assembly program to copy a file by name 97 | * [COPY.BAS](/cpc_files/COPY.BAS) - "copy" Basic entry program 98 | 99 | 100 | ## Future work 101 | 102 | - Creating RSX extensions for the copy and dir programs that can be loaded automatically from the Arduino by emulating a CPC ROM. This way, there is no need to have the dir and copy programs in 3.5" disks. 103 | - Creating a PCB based on an Arduino shield to make this project final and remove the breadboard (now covered in dust) from the back of my CPC. 104 | - Support ".DSK" image files directly in the SD card 105 | 106 | ## Reference 107 | 108 | - [Universal Serial Interface for Amstrad CPC (a.k.a USIfAC)](http://retroworkbench.blogspot.com/p/universal-serial-interface-for-amstrad.html) 109 | - [Arduino IO card for the CPC6128](https://hackaday.io/project/169565-arduino-io-card-for-amstrad-cpc-6128) 110 | -------------------------------------------------------------------------------- /assets/circuit1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpc/amstrad_cpc6128_interface/3c6e7ad69ebdea58c809024c34c2bc20d35010bb/assets/circuit1.jpeg -------------------------------------------------------------------------------- /assets/cpc_copy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpc/amstrad_cpc6128_interface/3c6e7ad69ebdea58c809024c34c2bc20d35010bb/assets/cpc_copy.jpeg -------------------------------------------------------------------------------- /assets/cpc_dir.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpc/amstrad_cpc6128_interface/3c6e7ad69ebdea58c809024c34c2bc20d35010bb/assets/cpc_dir.jpeg -------------------------------------------------------------------------------- /assets/schematic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1 10 | 11 | 12 | 13 | 14 | 2 15 | 16 | 17 | 18 | 19 | 3 20 | 21 | 22 | 23 | 24 | 4 25 | 26 | 27 | 28 | 29 | 5 30 | 31 | 32 | 33 | 34 | 6 35 | 36 | 37 | 38 | 39 | 7 40 | 41 | 42 | 43 | 44 | 8 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 1 61 | 62 | 63 | 64 | 65 | 66 | 67 | 2 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 3 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 2 97 | 98 | 99 | 1 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 2 114 | 115 | 116 | 1 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 2 131 | 132 | 133 | 1 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 2 148 | 149 | 150 | 1 151 | 152 | 153 | 154 | 155 | 156 | D13 PWM 157 | 158 | 159 | 160 | 161 | D12 PWM 162 | 163 | 164 | 165 | 166 | D11 PWM 167 | 168 | 169 | 170 | 171 | D10 PWM 172 | 173 | 174 | 175 | 176 | D8 PWM 177 | 178 | 179 | 180 | 181 | D9 PWM 182 | 183 | 184 | 185 | 186 | D7 PWM 187 | 188 | 189 | 190 | 191 | D6 PWM 192 | 193 | 194 | 195 | 196 | D5 PWM 197 | 198 | 199 | 200 | 201 | D4 PWM 202 | 203 | 204 | 205 | 206 | D3 PWM 207 | 208 | 209 | 210 | 211 | D2 PWM 212 | 213 | 214 | 215 | 216 | D1 TX0 217 | 218 | 219 | 220 | 221 | D0 RX0 222 | 223 | 224 | 225 | 226 | D14/TX3 227 | 228 | 229 | 230 | 231 | D15/RX3 232 | 233 | 234 | 235 | 236 | D16 PWM/TX2 237 | 238 | 239 | 240 | 241 | D17 PWM/RX2 242 | 243 | 244 | 245 | 246 | D18/TX1 247 | 248 | 249 | 250 | 251 | D19/RX1 252 | 253 | 254 | 255 | 256 | 257 | 258 | S 259 | 260 | 261 | D 262 | 263 | 264 | A 265 | 266 | 267 | 268 | 269 | 270 | 271 | SCL 272 | 273 | 274 | 275 | 276 | D22 277 | 278 | 279 | 280 | 281 | D23 282 | 283 | 284 | 285 | 286 | D24 287 | 288 | 289 | 290 | 291 | D25 292 | 293 | 294 | 295 | 296 | D26 297 | 298 | 299 | 300 | 301 | D27 302 | 303 | 304 | 305 | 306 | D28 307 | 308 | 309 | 310 | 311 | D29 312 | 313 | 314 | 315 | 316 | D30 317 | 318 | 319 | 320 | 321 | D31 322 | 323 | 324 | 325 | 326 | D32 327 | 328 | 329 | 330 | 331 | D33 332 | 333 | 334 | 335 | 336 | D34 337 | 338 | 339 | 340 | 341 | D35 342 | 343 | 344 | 345 | 346 | D36 347 | 348 | 349 | 350 | 351 | D37 352 | 353 | 354 | 355 | 356 | RESET 357 | 358 | 359 | 360 | 361 | AREF 362 | 363 | 364 | 365 | 366 | I 367 | 368 | 369 | O 370 | 371 | 372 | R 373 | 374 | 375 | E 376 | 377 | 378 | F 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 5V 390 | 391 | 392 | 393 | 394 | A0 395 | 396 | 397 | 398 | 399 | A1 400 | 401 | 402 | 403 | 404 | A2 405 | 406 | 407 | 408 | 409 | A3 410 | 411 | 412 | 413 | 414 | A4 415 | 416 | 417 | 418 | 419 | A5 420 | 421 | 422 | 423 | 424 | A6 425 | 426 | 427 | 428 | 429 | A7 430 | 431 | 432 | 433 | 434 | A8 435 | 436 | 437 | 438 | 439 | A9 440 | 441 | 442 | 443 | 444 | A10 445 | 446 | 447 | 448 | 449 | A11 450 | 451 | 452 | 453 | 454 | A12 455 | 456 | 457 | 458 | 459 | A13 460 | 461 | 462 | 463 | 464 | A14 465 | 466 | 467 | 468 | 469 | A15 470 | 471 | 472 | 473 | 474 | D38 475 | 476 | 477 | 478 | 479 | D39 480 | 481 | 482 | 483 | 484 | D40 485 | 486 | 487 | 488 | 489 | D41 490 | 491 | 492 | 493 | 494 | D42 495 | 496 | 497 | 498 | 499 | D43 500 | 501 | 502 | 503 | 504 | D44 505 | 506 | 507 | 508 | 509 | D45 510 | 511 | 512 | 513 | 514 | D46 515 | 516 | 517 | 518 | 519 | D47 520 | 521 | 522 | 523 | 524 | D48 525 | 526 | 527 | 528 | 529 | D49 530 | 531 | 532 | 533 | 534 | D50 535 | 536 | 537 | 538 | 539 | D51 540 | 541 | 542 | 543 | 544 | D52 545 | 546 | 547 | 548 | 549 | D53 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | GND 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 3V3 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | VIN 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | Arduino 601 | 602 | 603 | Mega 604 | 605 | 606 | 2560 607 | 608 | 609 | (Rev3) 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 1 618 | 619 | 620 | 621 | 622 | 2 623 | 624 | 625 | 626 | 627 | 3 628 | 629 | 630 | 631 | 632 | 4 633 | 634 | 635 | 636 | 637 | 5 638 | 639 | 640 | 641 | 642 | 6 643 | 644 | 645 | 646 | 647 | 7 648 | 649 | 650 | 651 | 652 | 8 653 | 654 | 655 | 656 | 657 | 9 658 | 659 | 660 | 661 | 662 | 10 663 | 664 | 665 | 666 | 667 | 11 668 | 669 | 670 | 671 | 672 | 12 673 | 674 | 675 | 676 | 677 | 13 678 | 679 | 680 | 681 | 682 | 14 683 | 684 | 685 | 686 | 687 | 15 688 | 689 | 690 | 691 | 692 | 16 693 | 694 | 695 | 696 | 697 | 17 698 | 699 | 700 | 701 | 702 | 18 703 | 704 | 705 | 706 | 707 | 19 708 | 709 | 710 | 711 | 712 | 20 713 | 714 | 715 | 716 | 717 | 21 718 | 719 | 720 | 721 | 722 | 22 723 | 724 | 725 | 726 | 727 | 23 728 | 729 | 730 | 731 | 732 | 24 733 | 734 | 735 | 736 | 737 | 25 738 | 739 | 740 | 741 | 742 | 26 743 | 744 | 745 | 746 | 747 | 27 748 | 749 | 750 | 751 | 752 | 28 753 | 754 | 755 | 756 | 757 | 29 758 | 759 | 760 | 761 | 762 | 30 763 | 764 | 765 | 766 | 767 | 31 768 | 769 | 770 | 771 | 772 | 32 773 | 774 | 775 | 776 | 777 | 33 778 | 779 | 780 | 781 | 782 | 34 783 | 784 | 785 | 786 | 787 | 35 788 | 789 | 790 | 791 | 792 | 36 793 | 794 | 795 | 796 | 797 | 37 798 | 799 | 800 | 801 | 802 | 38 803 | 804 | 805 | 806 | 807 | 39 808 | 809 | 810 | 811 | 812 | 40 813 | 814 | 815 | 816 | 817 | 41 818 | 819 | 820 | 821 | 822 | 42 823 | 824 | 825 | 826 | 827 | 43 828 | 829 | 830 | 831 | 832 | 44 833 | 834 | 835 | 836 | 837 | 45 838 | 839 | 840 | 841 | 842 | 46 843 | 844 | 845 | 846 | 847 | 47 848 | 849 | 850 | 851 | 852 | 48 853 | 854 | 855 | 856 | 857 | 49 858 | 859 | 860 | 861 | 862 | 50 863 | 864 | 865 | 866 | 74HC27N 867 | 868 | 869 | 870 | 1 871 | 1 872 | 873 | 874 | 875 | 2 876 | 2 877 | 878 | 879 | 880 | 3 881 | 3 882 | 883 | 884 | 885 | 4 886 | 4 887 | 888 | 889 | 890 | 5 891 | 5 892 | 893 | 894 | 895 | 6 896 | 6 897 | 898 | 899 | 900 | 7 901 | 7 902 | 903 | 904 | 905 | 14 906 | 14 907 | 908 | 909 | 910 | 13 911 | 13 912 | 913 | 914 | 915 | 12 916 | 12 917 | 918 | 919 | 920 | 11 921 | 11 922 | 923 | 924 | 925 | 10 926 | 10 927 | 928 | 929 | 930 | 9 931 | 9 932 | 933 | 934 | 935 | 8 936 | 8 937 | 938 | 939 | 940 | 74HC21N 941 | 942 | 943 | 944 | 1 945 | 1 946 | 947 | 948 | 949 | 2 950 | 2 951 | 952 | 953 | 954 | 3 955 | 3 956 | 957 | 958 | 959 | 4 960 | 4 961 | 962 | 963 | 964 | 5 965 | 5 966 | 967 | 968 | 969 | 6 970 | 6 971 | 972 | 973 | 974 | 7 975 | 7 976 | 977 | 978 | 979 | 14 980 | 14 981 | 982 | 983 | 984 | 13 985 | 13 986 | 987 | 988 | 989 | 12 990 | 12 991 | 992 | 993 | 994 | 11 995 | 11 996 | 997 | 998 | 999 | 10 1000 | 10 1001 | 1002 | 1003 | 1004 | 9 1005 | 9 1006 | 1007 | 1008 | 1009 | 8 1010 | 8 1011 | 1012 | SD CardQ110kΩ0.25W220Ω0.25CPC6128 Expansion PortNORAND -------------------------------------------------------------------------------- /compile_sketch.sh: -------------------------------------------------------------------------------- 1 | arduino-cli compile --fqbn arduino:avr:mega cpc6128_interface.ino 2 | 3 | -------------------------------------------------------------------------------- /cpc6128_interface.ino: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | //#define DEBUG 1 // debug on USB Serial 6 | 7 | #ifdef DEBUG 8 | #define DEBUG_PRINT(x) Serial.print (x) 9 | #define DEBUG_PRINT_HEX(x) Serial.print (x, HEX) 10 | #define DEBUG_PRINTLN(x) Serial.println (x) 11 | #define DEBUG_PRINTLN_HEX(x) Serial.println (x, HEX) 12 | #else 13 | #define DEBUG_PRINT(x) 14 | #define DEBUG_PRINT_HEX(x) 15 | #define DEBUG_PRINTLN(x) 16 | #define DEBUG_PRINTLN_HEX(x) 17 | #endif 18 | 19 | /* 20 | SD card attached to SPI bus as follows (mega): 21 | ** MOSI - pin 50 22 | ** MISO - pin 51 23 | ** CLK - pin 52 24 | ** CS - pin 53 (for MKRZero SD: SDCARD_SS_PIN) 25 | */ 26 | 27 | // Pin Mapping 28 | #define DATA_0 22 // PA0 29 | #define DATA_1 23 // PA1 30 | #define DATA_2 24 // PA2 31 | #define DATA_3 25 // PA3 32 | #define DATA_4 26 // PA4 33 | #define DATA_5 27 // PA5 34 | #define DATA_6 28 // PA6 35 | #define DATA_7 29 // PA7 36 | 37 | #define INT 2 // PE4 38 | 39 | #define WAIT 37 // PC0 40 | #define RD 36 // PC1 41 | #define BRST 35 // PC2 42 | #define ADDR_0 34 // PC3 43 | 44 | #define FILE_BUFFER 128 45 | 46 | #define CMD_DIR 1 47 | #define CMD_COPY 2 48 | 49 | volatile byte iorequest = 0; 50 | 51 | byte cmd = 0; // current command: 1 - dir() ; 2 - copy() 52 | 53 | class FileBuffer { 54 | char mFilename[12]; 55 | uint8_t mFilenameIndex = 0; 56 | uint16_t mFileCursor = 0; 57 | uint16_t mBufferIndex = 0; 58 | uint16_t mFileSize = 0; 59 | bool mReady = false; 60 | 61 | File mFile; 62 | byte mBuffer[FILE_BUFFER]; 63 | 64 | bool openFile() { 65 | mFile = SD.open(mFilename); 66 | if (mFile && mFile.available()) { 67 | mFileSize = mFile.size(); 68 | DEBUG_PRINT("Opening file '"); 69 | DEBUG_PRINT(mFilename); 70 | DEBUG_PRINT("' with size = "); 71 | DEBUG_PRINTLN(mFileSize); 72 | return true; 73 | } else { 74 | DEBUG_PRINT("File not found - "); 75 | DEBUG_PRINTLN(mFilename); 76 | return false; 77 | } 78 | } 79 | 80 | public: 81 | FileBuffer() { 82 | } 83 | 84 | void appendToFilename(char c) { 85 | mFilename[mFilenameIndex++] = c; 86 | } 87 | 88 | void reset() { 89 | memset(mFilename, 0, 12); 90 | mFilenameIndex = 0; 91 | mFileCursor = 0; 92 | mBufferIndex = 0; 93 | mFileSize = 0; 94 | mFile.close(); 95 | mReady = false; 96 | } 97 | 98 | uint16_t getFileSize() { 99 | return mFileSize; 100 | } 101 | 102 | byte getNextByte() { 103 | if (mReady && (mFileCursor < (mFileSize + 2))) { // +2 from file size word 104 | // add file size on the first 2 bytes 105 | 106 | if (mFileCursor == 0 || mBufferIndex == FILE_BUFFER) { 107 | DEBUG_PRINT("Buffering file content. Current Position: "); 108 | mBufferIndex = 0; 109 | DEBUG_PRINTLN(mFileCursor); 110 | mFile.read(mBuffer, FILE_BUFFER); // fill buffer 111 | } 112 | 113 | byte b; 114 | if (mFileCursor == 0) { 115 | b = (uint8_t) (mFileSize >> 8); // high byte 116 | } else if (mFileCursor == 1) { 117 | b = (uint8_t) (mFileSize & 0x00FF); // low byte 118 | } else { 119 | b = mBuffer[mBufferIndex++]; 120 | } 121 | 122 | mFileCursor++; 123 | return b; 124 | } else { 125 | DEBUG_PRINTLN("Error - no more data!"); 126 | return 0; 127 | } 128 | } 129 | 130 | bool hasMoreBytes() { 131 | return mFileCursor < (mFileSize + 2); 132 | } 133 | 134 | void init() { 135 | mFilename[mFilenameIndex] = 0; 136 | 137 | mReady = openFile(); 138 | } 139 | 140 | bool exists() { 141 | return mReady; 142 | } 143 | }; 144 | 145 | 146 | class FileInfo { 147 | private: 148 | char mFilename[12]; 149 | uint8_t mSize = 0; 150 | uint8_t mFilenameIndex = 0; 151 | 152 | public: 153 | FileInfo() { 154 | } 155 | 156 | FileInfo(char* filename, uint32_t size) { 157 | strcpy(mFilename, filename); 158 | mSize = size; 159 | } 160 | 161 | bool available() { 162 | return strlen(mFilename) > 0 && mFilenameIndex < 12; // < 12 including the suffix byte for size in kb 163 | } 164 | 165 | char nextChar() { 166 | if (available()) { 167 | if (mFilenameIndex == 11) { 168 | mFilenameIndex++; 169 | return mSize == 0 ? 1 : mSize; 170 | } 171 | return mFilename[mFilenameIndex++]; 172 | } else { 173 | return 0; 174 | } 175 | } 176 | }; 177 | 178 | // File iterator to send filenames to CPC. It keeps the state of the what was already sent and the current file. 179 | class FileIterator { 180 | private: 181 | File mRoot; 182 | FileInfo mFileInfo; 183 | 184 | void nextFile(FileInfo* outFileInfo) { 185 | while (true) { 186 | File entry = mRoot.openNextFile(); 187 | if (! entry) { 188 | // no more files 189 | outFileInfo = nullptr; 190 | break; 191 | } 192 | // ignores files bigger than 255 KB 193 | int sizeInKb = entry.size() / 1024; 194 | if (!entry.isDirectory() && (sizeInKb <= 255)) { 195 | char normalizedName[12]; 196 | normalizeFilename(entry.name(), normalizedName); 197 | *outFileInfo = FileInfo(normalizedName, (uint8_t) sizeInKb); 198 | break; 199 | } 200 | 201 | entry.close(); 202 | } 203 | } 204 | 205 | void normalizeFilename(char* name, char* outNormalizedName) { 206 | int name_len = strlen(name); 207 | 208 | // find last '.' char index 209 | int i_sep = name_len - 1; 210 | while (name[i_sep] != '.' && i_sep >= 0) { 211 | i_sep--; 212 | } 213 | 214 | // initialize array with spaces 215 | for (int i=0;i<11;i++) { 216 | outNormalizedName[i] = 0x20; 217 | } 218 | outNormalizedName[11] = 0; 219 | 220 | // set extension part 221 | for (int j=8, i=i_sep+1; j < 11 && i < name_len; j++, i++) { 222 | outNormalizedName[j] = name[i]; 223 | } 224 | 225 | // set name part 226 | for (int j=0; j < i_sep; j++) { 227 | outNormalizedName[j] = name[j]; 228 | } 229 | } 230 | 231 | public: 232 | FileIterator() { 233 | } 234 | 235 | void init(File root) { 236 | mRoot = root; 237 | } 238 | 239 | // closes the root file 240 | void release() { 241 | mRoot.close(); 242 | } 243 | 244 | char nextByte() { 245 | if (!mFileInfo.available()) { 246 | nextFile(&mFileInfo); 247 | if (!mFileInfo.available()) { 248 | return 0; 249 | } 250 | } 251 | return mFileInfo.nextChar(); 252 | } 253 | 254 | bool hasBytes() { 255 | if (!mFileInfo.available()) { 256 | nextFile(&mFileInfo); 257 | if (!mFileInfo.available()) { 258 | return false; 259 | } 260 | } 261 | return true; 262 | } 263 | }; 264 | 265 | FileIterator fileIterator; 266 | FileBuffer fileBuffer; 267 | 268 | void setup() { 269 | 270 | pinMode(ADDR_0, INPUT); 271 | pinMode(INT, INPUT_PULLUP); 272 | pinMode(RD, INPUT); 273 | 274 | digitalWrite(BRST, HIGH); 275 | pinMode(BRST, OUTPUT); 276 | 277 | digitalWrite(WAIT, LOW); 278 | pinMode(WAIT, OUTPUT); 279 | 280 | for ( int pin = DATA_7; pin >= DATA_0; pin-- ) { 281 | pinMode(pin, INPUT); 282 | } 283 | 284 | pinMode(LED_BUILTIN, OUTPUT); 285 | 286 | attachInterrupt(digitalPinToInterrupt(INT), ioreq, RISING); 287 | 288 | // Open serial communications and wait for port to open: 289 | Serial.begin(9600); 290 | while (!Serial) { 291 | ; // wait for serial port to connect. Needed for native USB port only 292 | } 293 | 294 | if (!SD.begin(53)) { 295 | DEBUG_PRINTLN("SD card initialization failed!"); 296 | while (1); 297 | } 298 | DEBUG_PRINTLN("SD card initialization complete."); 299 | } 300 | 301 | void ioreq() { 302 | iorequest++; 303 | } 304 | 305 | void loop() { 306 | if (iorequest == 1) { 307 | if (digitalRead(ADDR_0) == HIGH) { 308 | if (digitalRead(RD) == HIGH) { 309 | // Byte sent from cpc to control port 310 | readControlPort(); 311 | } else { 312 | // Byte was requested by cpc from the control port 313 | writeToControlPort(); 314 | } 315 | } else { 316 | if (digitalRead(RD) == HIGH) { 317 | // Byte sent from cpc to data port 318 | readDataPort(); 319 | } else { 320 | // Byte was requested by cpc from the data port 321 | writeToDataPort(); 322 | } 323 | } 324 | 325 | iorequest = 0; 326 | } 327 | } 328 | 329 | void writeToControlPort() { 330 | DEBUG_PRINTLN("Writing to Control Port..."); 331 | 332 | byte byte_to_send; 333 | 334 | switch(cmd) { 335 | case CMD_DIR: 336 | byte_to_send = fileIterator.hasBytes() ? 1 : 0; 337 | 338 | if (byte_to_send == 0) { 339 | fileIterator.release(); 340 | } 341 | break; 342 | case CMD_COPY: 343 | byte_to_send = fileBuffer.exists() ? 1 : 0; 344 | 345 | if (!fileBuffer.exists()) { 346 | fileBuffer.reset(); 347 | } 348 | 349 | break; 350 | default: 351 | break; 352 | } 353 | 354 | DEBUG_PRINT("[CTR] <-- 0x"); 355 | DEBUG_PRINTLN_HEX(byte_to_send); 356 | 357 | writeByte(byte_to_send); 358 | 359 | releaseWaitAfterWrite(); 360 | } 361 | 362 | void readControlPort() { 363 | DEBUG_PRINTLN("Reading from Control Port..."); 364 | 365 | cmd = readByte(); 366 | 367 | // assert command 368 | switch(cmd) { 369 | case CMD_DIR: 370 | DEBUG_PRINTLN("Received CPC command [DIR]"); 371 | fileIterator.init(SD.open("/")); 372 | 373 | break; 374 | case CMD_COPY: 375 | DEBUG_PRINTLN("Received CPC command [COPY]"); 376 | break; 377 | default: 378 | break; 379 | } 380 | 381 | releaseWaitAfterRead(); 382 | } 383 | 384 | void writeToDataPort() { 385 | DEBUG_PRINTLN("Writing to Data Port..."); 386 | 387 | byte byte_to_send; 388 | switch(cmd) { 389 | case CMD_DIR: 390 | byte_to_send = fileIterator.nextByte(); 391 | break; 392 | case CMD_COPY: 393 | byte_to_send = fileBuffer.getNextByte(); 394 | 395 | if (!fileBuffer.hasMoreBytes()) { 396 | fileBuffer.reset(); 397 | } 398 | break; 399 | default: 400 | break; 401 | } 402 | 403 | DEBUG_PRINT("[DATA] <-- 0x"); 404 | DEBUG_PRINTLN_HEX(byte_to_send); 405 | 406 | writeByte(byte_to_send); 407 | 408 | releaseWaitAfterWrite(); 409 | } 410 | 411 | void readDataPort() { 412 | DEBUG_PRINTLN("Reading from Data Port..."); 413 | 414 | byte b = readByte(); 415 | 416 | if (cmd == CMD_COPY) { 417 | DEBUG_PRINT("[DATA] --> 0x"); 418 | DEBUG_PRINTLN_HEX(b); 419 | 420 | fileBuffer.appendToFilename(b); 421 | 422 | if (b == 0) { 423 | fileBuffer.init(); 424 | } 425 | } 426 | 427 | releaseWaitAfterRead(); 428 | } 429 | 430 | byte readByte() { 431 | byte recvByte=0; 432 | __asm__ __volatile__( 433 | ".equ PORTA, 0x02 \n" 434 | ".equ PORTC, 0x08 \n" 435 | ".equ DDRA, 0x01 \n" 436 | ".equ PINA, 0x00 \n" 437 | ".equ PINE, 0x0c \n" 438 | "CLI \n" // Clear Global Interrupt 439 | "LDI r24, 0 \n" // Load r24 with 0 440 | "OUT DDRA, r24 \n" // Set all pins to inputs 441 | "IN %0, PINA \n" // read PINA (0-7) to <> 442 | 443 | : "=d" (recvByte)::"r24" 444 | ); 445 | return recvByte; 446 | } 447 | 448 | void releaseWaitAfterRead() { 449 | __asm__ __volatile__( 450 | "SBI PORTC, 0 \n" // Set bit 0 in PORTC - WAIT line HIGH 451 | "SBIC PINE, 4 \n" // Skip next instruction if Bit 4 (Interrupt) is Cleared 452 | "RJMP .-4 \n" // Relative Jump -4 bytes - 453 | "CBI PORTC, 0 \n" // Clear bit 0 in PORTC - WAIT line LOW 454 | "SEI \n" // Set Global Interrupt 455 | ); 456 | } 457 | 458 | void writeByte(byte toSend) { 459 | __asm__ __volatile__( 460 | ".equ PORTA, 0x02 \n" 461 | ".equ PORTC, 0x08 \n" 462 | ".equ DDRA, 0x01 \n" 463 | ".equ PINE, 0x0c \n" 464 | "CLI \n" // Clear Global Interrupt 465 | "LDI r25, 0xFF \n" // Load r25 with 0xFF - B11111111 466 | "OUT DDRA, r25 \n" // store r25 in DDRA - Set DDRA as output 467 | "MOV r25, %0 \n" // move byte register to r25 468 | "OUT PORTA, r25 \n" // Write byte to PORTA 469 | 470 | ::"r" (toSend):"r25" 471 | ); 472 | } 473 | 474 | void releaseWaitAfterWrite() { 475 | __asm__ __volatile__( 476 | "SBI PORTC, 0 \n" // Set bit 0 in PORTC - WAIT line HIGH 477 | "SBIC PINE, 4 \n" // Skip next instruction if Bit 4 (Interrupt) is Cleared 478 | "RJMP .-4 \n" // Relative Jump -4 bytes - 479 | "LDI r25, 0x00 \n" // Load r25 with 0x00 - B00000000 480 | "OUT DDRA, r25 \n" // store r25 in DDRA - Set DDRA as output again (default) 481 | "CBI PORTC, 0 \n" // Clear bit 0 in PORTC - WAIT line LOW 482 | "SEI \n" // Set Global Interrupt 483 | 484 | :::"r25" 485 | ); 486 | } 487 | -------------------------------------------------------------------------------- /cpc_files/COPY.BAS: -------------------------------------------------------------------------------- 1 | 10 MEMORY &7FFF 2 | 20 addr = &8000 3 | 30 INPUT "Filename";n$ 4 | 40 l=LEN(n$) 5 | 50 IF l>12 THEN 160 6 | 60 POKE addr, l 7 | 70 FOR i=1 TO l 8 | 80 c$=MID$(n$,i,1) 9 | 90 POKE addr+i, ASC(c$) 10 | 100 NEXT i 11 | 110 PRINT "Copying..." 12 | 120 LOAD "COPY.BIN" 13 | 130 CALL &8100 14 | 140 PRINT "Done." 15 | 150 END 16 | 160 PRINT "Invalid filename!" 17 | 170 GOTO 20 -------------------------------------------------------------------------------- /cpc_files/DIR.BAS: -------------------------------------------------------------------------------- 1 | 10 MEMORY &7FFFF 2 | 20 LOAD "DIR.BIN" 3 | 30 CALL &8000 -------------------------------------------------------------------------------- /cpc_files/copy.asm: -------------------------------------------------------------------------------- 1 | ; copy - transfers a file from Arduino to CPC 2 | 3 | UseTestData equ 0 ; 1 - use test data; 0 - use real IO 4 | 5 | cas_out_open equ &bc8c 6 | cas_out_close equ &bc8f 7 | cas_out_char equ &bc95 8 | 9 | PrintChar equ &BB5A 10 | 11 | filename_size equ &8000 12 | 13 | 14 | org &8100 15 | 16 | SendCmd: 17 | ld a, 2 ; Load GETFILE cmd into accumulator 18 | call SendControlByte ; Send GETFILE command 19 | 20 | ld hl, filename_size 21 | ld c, (hl) ; load C with filename size 22 | ld de, filename_size + 1 23 | SendFilenameByte: 24 | ld a, (de) ; load A with filename byte 25 | 26 | push bc 27 | call SendDataByte 28 | pop bc 29 | 30 | inc de 31 | dec c ; decrement filename pos 32 | jr nz, SendFilenameByte ; if C != 0 -> keep sending filename bytes 33 | 34 | ld a, 0 ; send \0 after filename 35 | call SendDataByte 36 | 37 | call RecvControlByte ; Receive filename result 38 | cp 0 39 | jp z, PrintFileNotFound ; Print FileNotFound message in case of 0 message from control port 40 | 41 | call RecvDataByte ; Receive file size high byte 42 | ld b, a 43 | 44 | call RecvDataByte ; Receive file size low byte 45 | ld c, a 46 | 47 | OpenOutFile: 48 | push bc 49 | ld hl, filename_size 50 | ld b, (hl) ; load B with filename size 51 | ld hl, filename_size + 1 ; load HL with filename start position 52 | ld de, two_k_buffer ; pass the 2k buffer 53 | call cas_out_open ; open output file 54 | pop bc 55 | 56 | next_byte: 57 | call RecvDataByte ; Read byte from IO 58 | push bc 59 | push hl 60 | call cas_out_char ; write byte to output file 61 | pop hl 62 | pop bc 63 | 64 | dec bc ; decrement count (BC = number of bytes remaining to write to output file) 65 | 66 | ld a, b 67 | or c 68 | jr nz, next_byte ; BC <> 0 -> not finished. write more bytes 69 | 70 | call cas_out_close ; BC = 0 -> finished writing - close the output file 71 | ret ; Finish! 72 | 73 | 74 | ; Prints File not found! message screen 75 | PrintFileNotFound: 76 | ld hl,FileNotFound ; load empty string mem loc into HL 77 | call PrintString 78 | call NewLine 79 | ret ; end program 80 | 81 | ; Print a '255' terminated string 82 | PrintString: 83 | ld a, (hl) ; load memory referenced by HL into register A 84 | cp 255 ; Compare byte with 255 85 | ret z ; return if A == 255 86 | inc hl ; increment HL 87 | call PrintChar 88 | jr PrintString 89 | 90 | NewLine: 91 | ld a,13 ; Carriage return 92 | call PrintChar 93 | ld a,10 ; Line Feed 94 | jp PrintChar 95 | 96 | 97 | #if UseTestData 98 | SendDataByte: 99 | jp TestSendDataByte 100 | SendControlByte: 101 | jp TestSendControlByte 102 | RecvDataByte: 103 | jp TestRecvDataByte 104 | RecvControlByte: 105 | jp TestRecvControlByte 106 | 107 | ; Test Routine for sending message - does nothing 108 | TestSendControlByte: 109 | TestSendDataByte: 110 | ret 111 | 112 | ; Test Routine for receiving byte. Instead of using IO, it will fetch the data from the 'TestData' var 113 | TestRecvControlByte: 114 | TestRecvDataByte: 115 | push hl ; push registers HL, BC, and DE into stack 116 | push bc 117 | push de 118 | 119 | ld hl,TestData ; load HL register with TestData address 120 | ld bc,TestDataPos ; load BC register with TestDataPos address 121 | 122 | ld d,0 ; Load DE register pair with the current memory position of the Test data 123 | ld a,(bc) 124 | ld e,a 125 | add hl,de 126 | 127 | inc a 128 | ld (bc), a 129 | 130 | ld a, (hl) ; load A register with the result byte 131 | 132 | pop de ; pop registers HL, BC, and DE from stack 133 | pop bc 134 | pop hl 135 | 136 | ret 137 | 138 | #else 139 | 140 | SendDataByte: 141 | jp DoSendDataByte 142 | SendControlByte: 143 | jp DoSendControlByte 144 | RecvDataByte: 145 | jp DoRecvDataByte 146 | RecvControlByte: 147 | jp DoRecvControlByte 148 | 149 | DoSendDataByte: 150 | ld c, &d0 ; Load C with low port byte 151 | ld b, &fb ; Load D with high port byte 152 | out (c), a ; Send the DATA byte 153 | ret 154 | 155 | DoSendControlByte: 156 | ld c, &d1 ; Load C with low port byte 157 | ld b, &fb ; Load D with high port byte 158 | out (c), a ; Send CONTROL byte 159 | ret 160 | 161 | DoRecvDataByte: 162 | ld a, &fb ; Load A with high port byte 163 | in a, (&d0) ; Read byte from IO data port 164 | 165 | ret 166 | 167 | DoRecvControlByte: 168 | ld a, &fb ; Load A with high port byte 169 | in a, (&d1) ; Read byte from IO control port 170 | 171 | ret 172 | 173 | #endif 174 | 175 | FileNotFound: 176 | db "File not found!", 255 177 | 178 | ;;---------------------------------------------------------------- 179 | ;; this is the filename of the output file 180 | 181 | filename: 182 | defb "datafile.bin" 183 | end_filename 184 | 185 | ;;---------------------------------------------------------------- 186 | ;; this buffer is filled with data which will be written to the output file 187 | 188 | two_k_buffer 189 | defs 2048 190 | 191 | #if UseTestData 192 | TestData: 193 | ; FileNotFound?, FileSize high byte, FileSize low byte, FileData 194 | db 1,1,0,0,84,69,83,84,32,32,32,32,66,65,83,0,0,0,0,0,0,0,0,0,112,1,0,21,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,21,0,0,49,3,19,0,10,0,191,34,66,114,117,110,111,32,67,111,110,100,101,34,0,0,0,26,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,19,0,10,0,191,34,66,114,117,110,111,32,67,111,110,100,101,34,0,0,0,26,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,97,26,160,32,30,20,0,0,0,0,26,32,3,0,0,225,1,190,32,13,0,0,233,44,255,29,40,34,38 195 | 196 | TestDataPos: 197 | db 0 198 | #endif 199 | -------------------------------------------------------------------------------- /cpc_files/copy.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpc/amstrad_cpc6128_interface/3c6e7ad69ebdea58c809024c34c2bc20d35010bb/cpc_files/copy.rom -------------------------------------------------------------------------------- /cpc_files/dir.asm: -------------------------------------------------------------------------------- 1 | ; dir - list all available files 2 | 3 | UseTestData equ 0 ; 1 - use test data; 0 - use real IO 4 | 5 | PrintChar equ &BB5A 6 | WaitChar equ &BB06 7 | 8 | org &8000 9 | 10 | SendDirCmd: 11 | ld a, 1 ; Load DIR cmd into accumulator 12 | call SendControlByte ; Send DIR command 13 | 14 | 15 | NextFile: 16 | call RecvControlByte 17 | cp 1 ; if we receive 1 from the control port, then we can start receiving the response 18 | jr nz, Finish 19 | 20 | call RecvFile 21 | 22 | ld a, (Count) 23 | inc a 24 | ld (Count), a ; if there are more than 255 files, this will fail miserably 25 | 26 | jr NextFile 27 | 28 | Finish: 29 | ld a, (Count) 30 | cp 0 31 | jp z,PrintEmpty ; A = 0 -> No files in directory -- Jump to empty msg 32 | 33 | ret ; end program 34 | 35 | #if UseTestData 36 | SendByte: 37 | jp TestSendByte 38 | SendControlByte: 39 | ret 40 | RecvByte: 41 | jp TestRecvByte 42 | RecvControlByte: 43 | jp TestRecvControlByte 44 | 45 | ; Test Routine for sending message - does nothing 46 | TestSendByte: 47 | ret 48 | 49 | ; Test Routine for receiving byte. Instead of using IO, it will fetch the data from the 'TestData' var 50 | TestRecvByte: 51 | push hl ; push registers HL, BC, and DE into stack 52 | push bc 53 | push de 54 | 55 | ld hl,TestData ; load HL register with TestData address 56 | ld bc,TestDataPos ; load BC register with TestDataPos address 57 | 58 | ld d,0 ; Load DE register pair with the current memory position of the Test data 59 | ld a,(bc) 60 | ld e,a 61 | add hl,de 62 | 63 | inc a 64 | ld (bc), a 65 | 66 | ld a, (hl) ; load A register with the result byte 67 | 68 | pop de ; pop registers HL, BC, and DE from stack 69 | pop bc 70 | pop hl 71 | 72 | ret 73 | 74 | TestRecvControlByte: 75 | ld a, (TestDataCount) 76 | cp 4 77 | ret z ; return if A == 4 78 | inc a 79 | ld (TestDataCount), a ; store incremented TestDataCount 80 | ld a, 1 ; return a with 1 81 | ret 82 | 83 | #else 84 | 85 | SendControlByte: 86 | jp DoSendControlByte 87 | RecvByte: 88 | jp DoRecvByte 89 | RecvControlByte: 90 | jp DoRecvControlByte 91 | 92 | DoSendControlByte: 93 | ld c, &d1 ; Load C with low port byte 94 | ld b, &fb ; Load D with high port byte 95 | out (c), a ; Send DIR cmd 96 | ret 97 | 98 | DoRecvByte: 99 | ld a, &fb ; Load A with high port byte 100 | in a, (&d0) ; Read byte from IO data port 101 | 102 | ret 103 | 104 | DoRecvControlByte: 105 | ld a, &fb ; Load A with high port byte 106 | in a, (&d1) ; Read byte from IO control port 107 | 108 | ret 109 | 110 | #endif 111 | 112 | ; Receive file name in the form: XXXXXXXXEEESF where: 113 | ; - X represents filename chars, 114 | ; - E file extension chars, 115 | ; - S the size of the file in KB, 116 | ; - F if the source filename is invalid - TODO 117 | RecvFile: 118 | ld hl,FileName ; load HL register pair with initial loc of FileName var 119 | ld b,11 ; expect exactly 11 chars in filename + extension 120 | RecvFileName: 121 | call RecvByte ; receive byte from file name 122 | ld (hl),a ; load filename var with received byte from A register 123 | inc hl ; increment HL 124 | dec b ; decrement b (filename + extension) 125 | ld a,b ; Load register B into Accumulator 126 | cp 3 ; have we reached 3 (extension)? 127 | call z, FileExtensionSep ; if A == 3 -> add '.' to file name buffer 128 | cp 0 ; have we reached 0? 129 | jr nz, RecvFileName ; if A != 0 -> keep receiving bytes 130 | call RecvByte ; Receive byte with file size 131 | ld hl,FileSize ; load HL register pair with FileSize var 132 | ld (hl),a ; store file size var 133 | 134 | call PrintFileInfo 135 | 136 | ret 137 | 138 | ; Add '.' between file name and extension 139 | FileExtensionSep: 140 | inc hl 141 | ld a,'.' ; Add a '.' between the filename and extension - Load A with '.' 142 | ld ix, FileName 143 | ld (ix+9),a ; Store '.' in filename 144 | ret 145 | 146 | ; Prints the file name and size to the screen 147 | PrintFileInfo: 148 | ld hl,FileName ; load HL register pair with initial loc of FileName var 149 | call PrintString 150 | 151 | ld a, ' ' ; Add spaces between filename and extension 152 | call PrintChar 153 | call PrintChar 154 | call PrintChar 155 | call PrintChar 156 | 157 | ; convert the file size byte in memory to ASCII 158 | ld bc,FileSize ; load BC register pair the FileSize var loc 159 | ld a,(bc) ; Load A with BC register - contains file size 160 | ld h,0 ; load H with 0 - High 161 | ld l,a ; load L with A - Low - file size 162 | ld de,FileSizeDec ; load DE register with the FileSizeDec var loc - mem to store the converted string 163 | call Num2Dec 164 | 165 | ld hl,FileSizeDec ; load HL register pair the FileSizeDec var loc 166 | call PrintString 167 | ld a, 'K' ; Load 'K' into A register 168 | call PrintChar 169 | call NewLine 170 | 171 | ret 172 | 173 | ; Prints the no files msg to the screen 174 | PrintEmpty: 175 | ld hl,Empty ; load empty string mem loc into HL 176 | call PrintString 177 | call NewLine 178 | ret ; end program 179 | 180 | ; Prints the not connected/timeout message screen 181 | PrintTimeout: 182 | ld hl,Timeout ; load empty string mem loc into HL 183 | call PrintString 184 | call NewLine 185 | ret ; end program 186 | 187 | ; Print a '255' terminated string 188 | PrintString: 189 | ld a, (hl) ; load memory referenced by HL into register A 190 | cp 255 ; Compare byte with 255 191 | ret z ; return if A == 255 192 | inc hl ; increment HL 193 | call PrintChar 194 | jr PrintString 195 | 196 | NewLine: 197 | ld a,13 ; Carriage return 198 | call PrintChar 199 | ld a,10 ; Line Feed 200 | jp PrintChar 201 | 202 | ; 16-bit Integer to ASCII (decimal) - adapted from http://map.grauw.nl/sources/external/z80bits.html 203 | Num2Dec: 204 | ld bc,-10000 205 | call Num1 206 | ld bc,-1000 207 | call Num1 208 | ld bc,-100 209 | call Num1 210 | ld c,-10 211 | call Num1 212 | ld c,b 213 | Num1: 214 | ld a,'0'-1 215 | Num2: 216 | inc a 217 | add hl,bc 218 | jr c,Num2 219 | sbc hl,bc 220 | 221 | cp a,'0' ; replace leading zeros with spaces 222 | jr nz, Num3 223 | ld a,' ' 224 | Num3: 225 | ld (de),a 226 | inc de 227 | ret 228 | 229 | Empty: 230 | db 'No files in directory.', 255 231 | 232 | Timeout: 233 | db 'Timeout!',13,10,'Is the Arduino connected to the CPC?', 255 234 | 235 | FileName: 236 | db 'XXXXXXXX.XXX',255 237 | 238 | FileSize: 239 | db 0 240 | 241 | FileSizeDec: 242 | db '00000',255 243 | 244 | Count: 245 | db 0 246 | 247 | #if UseTestData 248 | TestData: 249 | db 'FILEA BAS',3,'FILEB BIN',12,'BRUNO BAS',1,'CONDE BIN',5 250 | 251 | TestDataPos: 252 | db 0 253 | 254 | TestDataCount: 255 | db 0 256 | #endif -------------------------------------------------------------------------------- /cpc_files/dir.rom: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bmpc/amstrad_cpc6128_interface/3c6e7ad69ebdea58c809024c34c2bc20d35010bb/cpc_files/dir.rom -------------------------------------------------------------------------------- /monitor.sh: -------------------------------------------------------------------------------- 1 | screen /dev/cu.usbmodem1413401 9600 2 | -------------------------------------------------------------------------------- /upload_sketch.sh: -------------------------------------------------------------------------------- 1 | arduino-cli upload -p /dev/cu.usbmodem1413401 --fqbn arduino:avr:mega cpc6128_interface 2 | --------------------------------------------------------------------------------