├── .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 |
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 |
84 |
85 |
86 |
87 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------