-
Notifications
You must be signed in to change notification settings - Fork 4
Home
(CPC Ker-plink!)
A co-processor link card for Amstrad CPC Computers
This is a card which allows you to connect an external co-processor, like a RaspberryPi, directly to any Amstrad CPC Computer.
The card provides an easy to use parallel interface between the processors using First-In First-Out (FIFO) hardware and a simple message passing software protocol. Voltage level shifting components are included so that both 3V3 and 5V co-processors can be connected directly. Most likely the co-processor will be a RaspberryPi, but the interface is suitable for other processors and systems, including Arduinos, Teensy and PIC MCUs.
FIFO based operation ensures all key timing requirements are handled by the board, so that any co-processor can be easily interfaced without needing to run bare-metal, timing critical code. e.g. A Raspberry Pi running a full Rasbian (Linux) distribution and a relatively low performance language like Python is perfectly suitable. This kind of co-processor could make some of its own OS resources available to the CPC, for example an SD card interface, timers, serial links etc.
The board has a convenient connector to allow a RaspberryPi Zero to be plugged directly into the back of the card and powered from the FIFO card itself. Other Pis will fit (although access to the HDMI port is awkward at the bottom of the card), but can more easily be connected using a short 50W ribbon cable. Many other CPUs including Arduino, Teensy, ChipKit, PIC MPUs can be connected with a suitable cable adapter.
Here is a list of the key features:
- suitable for all CPC models
- uses a 50W box connector for compatibility with popular expansion boards
- compatible with external processors using either 5V or 3.3V IO signalling
- 3V3 coprocessors need to supply a 3V3 reference voltage to the card via a dedicated connector pin
- RaspberryPi compatible socket on rear
- plug in a Pi Zero and power directly from the FIFO card
- other (larger) Pi models or other CPUs incl. Arduino, Teensy can connect with a cable adapter
- dedicated UART pins on front side of card for connection through to co-processor
- Simple access protocol
- Byte wide, 16 byte deep FIFO in both directions with status flags
- separate status register and data register mapped into CPC IO port space
- byte wide data bus and flags/control signals driven by coprocessor GPIO
- Documentation and support files provided
- programming and construction notes
- Several examples of CPC and RaspberryPi code for simple loopback testing
- Python and C coprocessor examples for Raspberry Pi
- BASIC, BCPL/assembler and C examples for Amstrad CPC
- Assember coded libraries are provided for both BCPL and C
- Loopback examples demonstrate data rates of
- 50KBytes/s with the CPC checking FIFO flags byte by byte
- 100KBytes/s using block transfers (coprocessor checking for overrun only)
- Higher rates are possible with more loop unrolling/burst transfer protocols
- Simple construction using cheap and easily available parts
- all through-hole, dual in line package, 74 Series logic design
- no programmable CPlD,FPGA,GAL,MPU or other parts
- Fully open source project licensed under the GNU GPL3
- includes all PCB layout (Eagle and gerber) and logical netlist files
The project has been fully built and tested.
An initial prototype (v0.2) was built using a Xilinx XC9536 CPLD to implement all logic. This is fully functional, but the CPLD is sadly now obsolete. Full design and construction details are included but this version is not recommended for new builds.
A second version of the card (v1.0) with all logic implemented in standard 74 series gates has been built and tested now. This is fully working and totally software compatible with the original prototype. This is the recommended build.
Full details of the connector pin assignments and GPIO assignments on RaspberryPi are provided later in the Hardware section. The coprocessor needs to have 13 pins available, connected as follows
Signal Name | Direction (Relative to Copro.) | #Pins | Idle State | Comment |
---|---|---|---|---|
DATA | Bidirectional | 8 | Hi-Z | Data in and out of the FIFO card |
DIR | Input | 1 | NA | Data Input Ready from FIFO card |
DOR | Input | 1 | NA | Data Output Ready from FIFO card |
SI | Output | 1 | 0 | Shift Data In to FIFO card (active low to high edge) |
SOB | Output | 1 | 0 | Not Shift Data Out to FIFO card (active high to low edge) |
WriteNotRead | Output | 1 | 0 | Signal Write (1) or Read (0) to FIFO Card |
When the interface is inactive the co-processor should drive or tristate the pins to the idle states as shown in the table above. This puts the FIFO in 'read mode' and the bidirectional data pins will be driven by the FIFO without any electrical conflict.
When data is available to the copro, the DOR signal will go high.
To read the data the copro runs through the following sequence
- Read data on the DATA inputs
- Pulse the SOB signal high then low
When the FIFO has space for data from the copro, the DIR signal will go high.
To write data to the FIFO the copro runs through the following sequence
- Drive the WriteNotRead signal high
- Drive the byte to be written onto the DATA lines
- Pulse the SI signal high then low
Take care that the SOB or SI pulse does not violate the minimum pulse width specification in the 75HCT40105 Datasheet. In the C example (see the sw/rpi_c directory) I found that a 'delayMicroseconds(60)' is needed for the pulse width to get reliable operation at 5V.
The FIFOs appear to the CPC as IO mapped locations at &FD81 and & FD80.
- IO Address &FD80 is the FIFO data register
- IO Address &FD81 is the FIFO status register
NB. To simplify address decoding and reduce the chip count on the 74 Series implementation of the FIFO board, the board actually requires 8 locations in IO space from &FD80 to &FD87. In this way, the FIFO registers appear mapped to every pair of even/odd addresses in this space.
Bits in the status register are assigned as follows
bit | Name | Function |
---|---|---|
2-7 | Unused | always return 'x' on reading |
1 | DIR | Data Input ready |
0 | DOR | Data Output Ready |
The DOR and DIR bits are read-only. When reading the status register the upper 6 bits must always be masked off since they are not driven by the FIFO card.
Writing any value to the status register will reset the FIFOs, and this should be done on starting up the interface.
Unlike the Co-processor side of the interface the FIFO IC Shift In (SI) and Not Shift Out (SOB) signals are not brought to the host side. These are generated on the fly by the board logic because a 4MHz Z80 IO cycle (which always includes an automatic wait state) is guaranteed to always meet the required pulse widths.
The read and write process for the host is much simpler than for the coprocessor.
When data is available to the host, the DOR bit in the status register will go high. So to read from the FIFO the host just needs to poll the FIFO status register and if the DOR bit is set to '1' then read from the FIFO Data IO register.
When the FIFO has space to write new data from the host, the DIR bit in the status register will go high. So again the procedure is just to poll the FIFO status register and if the DIR bit is set to '1' then write to the FIFO Data IO register.
This is a short and rather pedestrian demonstration of the FIFO in use between the CPC using BASIC, and a RaspberryPi (RPi) running Python in a full Raspbian (Linux) distribution.
The Python code for the RPi uses the standard GPIO.RPi library to control the GPIO pins. The code demonstrates how to check flags and read/write bytes to the FIFO as the RPi loops endlessly, listening for incoming data on one FIFO and echoing it back out through the other.
import RPi.GPIO as gpio
from collections import deque
#define BCM Pin Allocations
PIN_DATA = [11,10,9,8,7,4,3,2] # Data[7:0]
PIN_DIR = 17
PIN_SI = 18
PIN_SOB = 22
PIN_DOR = 23
PIN_WNR = 24
def setup_pins():
gpio.setmode(gpio.BCM)
gpio.setup(PIN_DATA,gpio.IN)
gpio.setup(PIN_DIR, gpio.IN)
gpio.setup(PIN_DOR, gpio.IN)
gpio.setup(PIN_SI, gpio.OUT, initial=gpio.LOW)
gpio.setup(PIN_SOB, gpio.OUT, initial=gpio.LOW)
gpio.setup(PIN_WNR, gpio.OUT, initial=gpio.LOW)
def write_fifo_byte(txdata):
gpio.output(PIN_WNR,gpio.HIGH)
for (b,d) in zip(PIN_DATA,txdata):
gpio.setup(b, gpio.OUT, initial=d)
gpio.output(PIN_SI, gpio.HIGH)
gpio.output(PIN_SI, gpio.LOW)
for b in PIN_DATA:
gpio.setup(b, gpio.IN)
gpio.output(PIN_WNR, gpio.LOW)
def read_fifo_byte():
rcv = [gpio.input(b) for b in PIN_DATA]
gpio.output(PIN_SOB, gpio.HIGH)
gpio.output(PIN_SOB, gpio.LOW)
return(rcv)
if __name__ == "__main__":
setup_pins()
write_queue = deque(maxlen=8192)
while True:
if gpio.input(PIN_DOR):
write_queue.append(read_fifo_byte())
if len(write_queue)>0 and gpio.input(PIN_DIR):
write_fifo_byte(write_queue.popleft())
The BASIC code for the CPC creates an array of 1024 random bytes, transmits the bytes to the RPi and receives them back from the RPi before checking that received data matches transmission and reporting data rates and any errors
10 DEFINT a-z
20 MODE 2
30 DIM tx[1024]: 'transmit data buffer
40 DIM rx[1024]: 'receive data buffer
50 OUT &FD81,0
60 PRINT "Setting up random data"
70 FOR i=0 TO 1023:tx[i]=INT(RND*256):NEXT i
80 i=0: 'tx byte count
90 j=0: 'rx byte count
100 PRINT "Sending/Receiving Data"
110 s!=TIME
120 WHILE i+j<>2048
130 IF i<>1024 AND (INP(&FD81) AND 2) THEN OUT &FD80,tx[i] :i=i+1
140 IF j<>1024 AND (INP(&FD81) AND 1) THEN rx[j]=INP(&FD80):j=j+1
150 WEND
160 dur!=(TIME-s!)/300:'timer in 300ths of sec
170 PRINT "Bytes sent: ";i
171 PRINT "Bytes Received: ";j
172 PRINT "Time: ";dur!;" s "
173 PRINT "Data rate: ";(1024*2)/dur!;" Bytes/s"
180 PRINT "Checking Data ...";
190 e=0
200 FOR i=0 TO 1024:IF rx[i]<>tx[i] THEN e=e+1: PRINT rx[i],tx[i]: NEXT
210 IF e=0 THEN PRINT "No errors" ELSE PRINT e;" errors detected"
220 END
To run the demo, save the python script as loopback.py on the RPi and the BASIC code as fifo.bas on the CPC (both are included in the sw/ directory). Then startup the RPi script first:
python loopback.py
Now start the CPC running:
RUN "FIFO.BAS
and you should see something like this
The CPC can support a data rate of around 50KBytes/s (if checking the status flag for each byte), but you will see numbers much lower than this with this BASIC demo. The main limitation here is the CPC BASIC loop: even without checking flags the loop takes around 13s to execute. Enabling the Pi adds nothing measurable to that and RPi.GPIO is known as one of the slower libraries executing in an interpreted language here.
So, some faster CPC code is called for to do a better job and as a first stab at this there is a BCPL version of the CPC FIFO code in the sw/cpc_bcpl directory. This one is intended for compiling with Arnor's CPC BCPL and is already more than 20x faster than the BASIC when reading/writing a single byte at a time. More impressive speedups are possible using the fifolib.b library (written in assembler) to send/receive strings of bytes instead. With these libraries, rates of about 6KBytes/s can be achieved and at this point it's the interpreted Python on the Raspberry Pi which is the bottleneck. Moving to C with the WiringPi library on the Pi and that 50KBytes/s data rate can be obtained.
In fact the Pi Zero is able to keep up with the CPC, so moving to a block transfer mode where only the Pi checks the FIFO flags can more than double this data rate, as shown in the screenshot below.
Finally, a version of the fifolib is provided for use with SDCC (CPCTelera) together with another program example in the sw/cpc_cpct directory. The C code is better than Arnor's BCPL when dealing with the byte-at-a-time scenario, but for all the other runs which spend most of their time in the assembler coded libraries, the run times are about the same.
All the example progams are provided in the sw/ directory.
Note that the blind block transfers aren't 100% reliable with the RaspberryPi Zero. Faster RPis with more cores may do better, but really this kind of block transfer needs to be accompanied by some kind of error checking protocol which can schedule packet retransmission. This maybe worthwhile for things like a mass-storage application, but for sending smaller packets the 50KBytes/s rates using the full handshaking are 100% reliable and will be easier to use.
Below is a table showing data rates from combining the various example loopback programs for CPC and RaspberryPi Zero:
<style> </style>Host | Co-Processor | Bytes | Run Time | Data rate | Comment | ||||||
Machine | OS | Language | Code | Machine | OS | Language | GPIO Lib | Transferred | (s) | Bytes/s | |
CPC464 | AMSDOS | Locomotive BASIC 1.2 | fifo.bas | Pi Zero | NA | NA | NA | 2048 | 13.33 | 153.6 | Baseline test, checking BASIC runtime ignoring FIFO flags |
CPC464 | AMSDOS | Locomotive BASIC 1.2 | fifo.bas | Pi Zero | Raspbian | Python | RPi.GPIO | 2048 | 13.33 | 153.6 | |
CPC464 | AMSDOS | Locomotive BASIC 1.2 | fifo.bas | Pi Zero | Raspbian | Python | WiringPi | 2048 | 13.33 | 153.6 | |
CPC6128 | AMSDOS | Locomotive BASIC 1.2 | fifo.bas | Pi Zero | Raspbian | C | WiringPi | 2048 | 13.33 | 153.6 | |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | RPi.GPIO | 16384 | 4.12 | 3976.7 | Single byte transfers, checking status each byte |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | RPi.GPIO | 16384 | 2.8 | 5851.4 | Multi byte transfers, checking status each byte |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | RPi.GPIO | 16384 | FAIL | NA | Block transfer mode, no status checks |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | RPi.GPIO | 16384 | FAIL | NA | Block transfer mode, no status checks, unrolled 4 byte loops |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | WiringPi | 16384 | 4.09 | 4005.9 | Single byte transfers, checking status each byte |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | WiringPi | 16384 | 2.56 | 6400 | Multi byte transfers, checking status each byte |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python C390 | WiringPi | 16384 | FAIL | NA | Block transfer mode, no status checks |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | Python | WiringPi | 16384 | FAIL | NA | Block transfer mode, no status checks, unrolled 4 byte loops |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | C | WiringPi | 16384 | 4.09 | 4005.9 | Single byte transfers, checking status each byte |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | C | WiringPi | 16384 | 0.33 | 49648 | Multi byte transfers, checking status each byte |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | C | WiringPi | 16384 | 0.2 | 81920 | Block transfer mode, no status checks |
CPC6128 | AMSDOS | Arnor BCPL/asm | fifo.b | Pi Zero | Raspbian | C | WiringPi | 16384 | 0.13 | 126030.8 | Block transfer mode, no status checks, unrolled 4 byte loops |
CPC6128 | AMSDOS | C (SDCC/CPCTelera) + fiflib.s | fifo.c | Pi Zero | Raspbian | C | WiringPi | 16384 | 1.42 | 11538 | Single Byte Transfers, full status checks |
CPC6128 | AMSDOS | C (SDCC/CPCTelera) + fifolib.s | fifo.c | Pi Zero | Raspbian | C | WiringPi | 16384 | 0.33 | 49648 | Block transfer mode, unrolled 2 byte loops |
CPC6128 | AMSDOS | C (SDCC/CPCTelera) + fifolib.s | fifo.c | Pi Zero | Raspbian | C | WiringPi | 16384 | 0.16 | 102400 | Block transfer mode, no status checks, unrolled 2 byte loops |
A 40W connector is provided for the coprocessor and is configured so that a Raspberry pi can be plugged in directly on the back of the board. Other devices, e.g. Arduino, will need an adapter cable.
The pin-out of the connector and RaspberryPi GPIO connections are shown below.
Pin | Pi Pin name | Function |
---|---|---|
1 | 3V3 | 3V3 Power into FIFO Board |
2 | 5V | 5V Power from FIFO Board |
3 | GPIO2 (SDA1) | FIFO DATA [0] |
4 | 5V | 5V Power from FIFO Board |
5 | GPIO3 (SCL1) | FIFO DATA [1] |
6 | GROUND | GROUND |
7 | GPIO4 | FIFO DATA [2] |
8 | GPIO14 (TxD) | UART TxD |
9 | GROUND | GROUND |
10 | GPIO15 (RxD) | UART RxD |
11 | GPIO17 | FIFO DATA INPUT READY (DIR) |
12 | GPIO18 | FIFO SHIFT IN (SI) |
13 | GPIO27 | unused |
14 | GROUND | GROUND |
15 | GPIO22 | NOT FIFO SHIFT OUT (SOB) |
16 | GPIO23 | FIFO DATA OUTPUT READY (DOR) |
17 | 3V3 | 3V3 Power into FIFO board |
18 | GPIO24 | FIFO WRITE NOT READ |
19 | GPIO10 (MOSI) | FIFO DATA[6] |
20 | GROUND | GROUND |
21 | GPIO09 (MISO) | FIFO DATA[5] |
22 | GPIO25 | unused |
23 | GPIO11 (SCLK) | FIFO DATA[7] |
24 | GPIO08 (CE0) | FIFO DATA[4] |
25 | GROUND | GROUND |
26 | GPIO07 (CE1) | FIFO DATA [3] |
27 | SDA0 (EEPROM) | unused |
28 | SCL0 (EEPROM) | unused |
29 | GPIO05 | unused |
30 | GROUND | GROUND |
31 | GPIO06 | unused |
32 | GPIO12 | unused |
33 | GPIO13 | unused |
34 | GROUND | GROUND |
35 | GPIO19 | unused |
36 | GPIO16 | unused |
37 | GPIO26 | unused |
38 | GPIO20 | unused |
39 | GROUND | GROUND |
40 | GPIO21 | unused |
Note that only the pins of the original RaspberryPi's 26W connector are used, so that the card is directly plug compatible with all Pis and with the IO board fitted in the Fuze T2 keyboard for both RPi1 and RPi2 revisions. However some GPIOs were renumbered after the very first Rev1 version of the RPi 1. The pin-out here applies to all current RaspberryPi models. For GPIO changes to use a Rev1 pi refer to SwiftyGPIO.
When using a RaspberryPi the Pi's UART RxD and TxD Signals are both brought out to pins on a three pin header so that a serial debugger can be easily connected. Arduinos offer similar features so when adapting one of those into the 40 way socket these pins should connect to the appropriate Arduino IOs.
UART Pins and Header Details
To use the RaspberryPi serial port, the Pi's serial terminal needs to be enabled (usually done via the raspi-config utility) and then a USB/TTL serial cable can be used with the data-rate set to 115200.
The board has two jumpers affecting power to the co-processor header and the operating voltage on the interface:
- Jumper J1 : VDDIO jumper
- Jumper J2 : Copro +5V power
J1 is a 3 way header to select either 3V3 or 5V power for the two level shifter ICs. The 3V3 supply, if selected, is taken from the relevant pin on the 40W connector and must be supplied by the external card (usually a RaspberryPi). The 5V supply is always taken from the host power supply.
J2 is a simple 2 way header which will provide host +5V power from to the 40W connector when closed. This is intended mainly so that a small RaspberryPi Zero, or other low current variant, can be plugged into and powered from the FIFO card. For all other uses this jumper should be left open and external power provided to the coprocessor separately. For example, you must not close this jumper when using an RaspberryPi which is also being powered by its own USB input - that would short the two power supplies for which neither card has protection.
Depending on the FIFO board supply from the CPC it may be possible to power other models from the FIFO card, but recommended settings with RaspberryPis are
Pi Model | J1 | J2 | Pi Power |
---|---|---|---|
A, A+, B+, 3A+, Zero, W | 3V3 | Closed | FIFO board supply |
All other models | 3V3 | Open | External USB supply |
WARNING do not close J2 when any coprocessor is attached to the 40W connector and powered by an external (e.g. USB) supply.
The recommended build version is v1.0, using all standard 74 series components. Full details of the CPLD based prototype design and build are still available in the project and more notes on board construction for both versions are provided on the following pages:
All programs and data files in this project are made available under the terms of the GNU General Public License v3.
CPC-CPlink Plugged into the 4 slot expansion board
Rear view of CPC-CPlink Plugged into the 4 slot expansion board with Pi Zero Attached
Old School Reunion - the CPC-CPlink together with the 512KByte CPC6128 RAM Expansion card and 4 slot expansion board