Project 022 - ZX Spectrum Multi I/O Interface

DISCLAIMER: This design is experimental, so if you decide to build one yourself then you are on your own, I can't be held responsible for any problems/issues/damage/injury that may occur if you decide to follow this build and make one yourself.

INTRO

final5

The project - To design and built a Multi function I/O board for the ZX Spectrum.........

Back in the early 80's when I was a teenager I bought myself a 16k Sinclair ZX Spectrum computer, but rather than play games I spent all my time playing about with BASIC writing some wierd and wonderful programs. I also got into the actual hardware behind the Spectrum which of course is the Z80A Cpu by Zilog. At work we also used the Z80A so it was fun and easy for me to play around with the hardware building the odd interface and gleaning info from those around me more in the know of the Z80A.

Roll the clock on some 30 years and I find myself with the ZX Spectrum once again on the bench in-front of me and looking rather curiously at the edge connector at the back............hmmmm, I wonder!

A lot of my workshop projects recently have been with the Arduino and enjoying interfacing lots of analogue and digital signals, so I thought wouldn't it be nice if the Spectrum had similar analogue and digital I/O capability. So, I set to work and came up with a specification and a number of IC's I'd used previously with the Arduino and thought they might be easily ported to the ZX Spectrum. If anything a nice challenge!

  • ADC - 4 analogue inputs - 12 to 18bit configurable
  • DAC - 4 analogue outputs - 16bit
  • 24 digital I/O
  • I2C bus interface

The I2C interface is key to the design as I wanted to use the same I2C based ADC & DAC IC's that i'd used before (with the Arduino). Therefore, I'd need to find a converter IC that would allow this, i.e Spectrum data bus to I2C conversion. The following is the basic hardware behind the entire interface.

With the above and a few additional components the basics of the hardware design is there, and so I set to prototype a pcb, and then design a full board using Eagle PCB.

Note: This design has only been tested with a 48K ZX Spectrum rubber keys model. I have not tested it at all with any 128k Spectrums so am unsure of any compatibility there.

DCS Electronics now have the rights over the Multi I/O board - https://www.dcselectronics.co.uk/zx-spectrum-multiface.html

Sinclair

CIRCUIT DESIGN

The following is a brief rundown of the circuit.

POWER SUPPLY:
I chose to run the interface from the unregulated DC (9 to 12vdc) rather than the Spectrum's own +5vdc regulated supply. A couple of regulators have been used supplying 3.3vdc & 5.3vdc to the circuit. U6 the PCA9464D parallel to I2C IC requires 3.3vdc (but can handle 5vdc logic I/O). Everything else runs from 5.3vdc, choosing an LM317 to generate this voltage. I chose 5.3vdc so that I could guarantee 0-5vdc on the analogue side of the DAC & ADC. The Vref for the DAC is 5.000vdc (+/- 0.1%). All IC's on the board are spec'd to run at up to 5.5vdc so no problems there.

I/O DECODING:
The first thing to do is connect to the ZX Spectrum's interface, or to be more exact the 8-bit data bus of the Z80A Cpu. This is pretty easy normally and can be done in a number of ways using just one IC for the IO address decoding, and I chose the 74LS138 1 of 8 Decoder - http://www.ti.com/lit/ds/symlink/sn74ls138.pdf
One caveat with the 74LS138 though is that it will not carry full I/O decoding of the entire Z80's address range (A0 to A15) but actually only 3, and in particular I used A1, A2 & A3 thus accessing even adresses only as I believe the ULA utilizes a load of the odd addreses). By doing this not only limits the IO addresses that can be used, but also opens up the fact that many IO addresses share the same bottom addresses and thus possible conflict. Many ZX Spectrum interface designers over the years have done exactly that in order to keep costs down given the relatively low likelyhood that Joe Bloggs will buy 2 interfaces that conflict. For the design I also chose to do it this way. All said and done, the 74LS138 gives me 8 selectable lines I could use as chip selects (CS), and in the end only 2 were required.
In saying that, it wasn't long before I started experiencing an issue during prototyping i.e. glitching on 5 of the 8 chip selects, a little unusual and I had my suspicions on the Spectrum's internal design (the ULA etc!) which isn't your normal Z80A processor board and so I added an extra gate to tie in the RD/WR lines from the Cpu rather than lying on the Z80's IORQ line. In the end, much more reliable and glitch free.

I2C:
A PCA9564 IC is a parallel to I2C bus converter IC and gives the Spectrum an inductry standard I2C interface. The primary function is to interface to the I2C enabled ADC & DAC IC's, but I have also connected the SCL & SDA bus lines to a couple of screw terminals at the back of the board. This will allow the user to extend the bus to other devices or boards.
There is one caveat with this IC though and it's not quite as straight forward in the software as it is with say the Arduino, look up the code for more info.

ADC:
A simple to implement circuit using a MCP3424 quad channel ADC. No Vref is required as it's internal to the IC, and so all I have done is add a voltage divider / low pass filter on each of the four inputs. This gives each input a range of 0 - 10vdc. Resolution is configurable in the software from 12bit, 14bit, 16bit & 18bit. The software default i've set to 16bit to match the DAC.
In real life terms at 16bit I am certainly getting 1-bit stability from an analogue input which isn't too bad considering the sheer noise that can be found on the ZX Spectrums ground plane.

DAC:
I used a DAC8574 quad channel DAC. A 5.00vdc Vref sets the FSD for the outputs, and I used a rail-to-rail (input & output) chopper op-amp on each DAC output to guarantee a stable and accurate output. Optional resistors/caps allow for scaling the output accordingly. By default they are hooked up as voltage followers. Resolution is set at 16bit.
Note: If there's area in the design/layout I would have liked to improve then that would be the DAC, since despite setting VrefLO and VrefHI accordingly I am getting some slight offsets (in order of a few mV's)! Saying that though, stability is certainly in the order of tens of micro volts (uV).

DIGITAL INPUTS / DIGITAL OUTPUTS:
The 82C55 takes care of the digital I/O. This IC interfaces direct to the Z80A's data bus and is fairly easy to setup to control the three 8-bit ports. There's much more to this IC than meets the eye, but for simplicity I've set it up as follows:
Port A = 8 x digital inputs, pulled high to +5v, active low to GND.
Port B = 8 x digital outputs via ULN2803 driver, V+ supplied externally (any voltage).
Port C = Bit 7 is reserved (CS to PCA9564), the remaining 7 bits are configured to drive 3 on-board LEDs and read 4 on-board jumper headers. Upper nibble = 4off outputs, lower nibble = 4off inputs.

VIDEO:
I did record a video blog as I had to troubleshoot an I2C problem during development. See here.

I/O TESTING / SPECIFICATIONS:
The calculated & tested spec. is as follows.

16bit Analogue Output = Range 0 - 65535, 0 - 5vdc, 0.000076vdc per bit (0.076mV or 76uV) calculated.
Voltage at DAC output: 0bits = 0.01463vdc, 32767bits = 2.51308vdc, 65535bits = 5.01020vdc
Voltage at Op-amp output: 0bits = 0.01429vdc, 32767bits = 2.51270vdc, 65535bits = 5.010028vdc
Note: Most DACs don't work down to 0.000vdc very well, this particular DAC specs a zero scale error of 5 to 20mV (I measured 14mV on my particular DAC), see datasheet for explanation.
Note: DAC tested using my uncalibrated bench 6.5digit DMM. I really need to send it away for recalibration and will revisit this once thats done.

16bit Analogue Input = Range 0 - 65535, 0 - 10vdc, 0.000152vdc per bit (0.152mV or 152uV) calculated.
Real life figures = 0.000vdc = 0bit, 2.500vdc = 7994bits, 5.000vdc = 15988bits, 7.500vdc = 23973bits, 10.000vdc = 31966bits
Note: Better absolute accuracy would be attained by using precision resistors on the input voltage divider.
Note: ADC tested using my cheap Ebay Hao Qi Xin voltage ref. (based on AD584LH, 1mV accuracy).

Digital outputs = ULN2803 spec. gives 50vdc max., 500mA per channel, on-board back-emf/clamp diodes. Datasheet here.

Digital inputs = 10k pull-up to +5.3vdc, external connection via contact to 0vdc (GND).

I2C = Bi-directional I2C communication IC standard & fast mode capable. Max. master mode freq. = 360kHz, Max. slave mode freq. = 400kHz. Can operate as master, slave, transmitter or receiver.

GOING FORWARD:
I'd like to experiment further with the external I2C bus connections and possibly try interfacing to some other I2C devices such as a graphics/alphanumeric LCD.

pcbannotate

EAGLE PCB FILES & ZX BASIC CODE

I have made available all the design files as I don't intend to sell the PCB's long term, except maybe a limited edition run of 3 or 4. Please contact me if you are interested (fully assembled).

PCB:
Current version = V1.0
Schematic in PDF format - here.
Eagle PCB files - here.
BOM file - here (supply part numbers where applicable).

CODE:
ZX Basic (Boriel) code here. The zip includes a compiled .TAP file as well as the .BAS basic file.
The BASIC code does not run natively on a ZX Spectrum. It requires to be compiled to a .TAP (machine code) file using Boriel's ZX Basic Compiler. See http://www.boriel.com/en/software/the-zx-basic-compiler/
The code by default runs some subroutines to demonstrate how to read and write to both digital & analogue I/O, the idea being that i've provided all the base information for anyone to take the system forward for their own bespoke work.

 
PHOTOS

Here's the final version of the board (top of photo) connected to the back of a Spectranet card which is plugged into the ZX Spectrum. The Spectranet card gives the Spectrum an IP address so I could upload the TAP file to the Spectrum easily and quickly via a Lan connection. Luckily it has a full thro' connector so my board could go at the back.
You can see a test relay and external LED connected to a couple of the digital outputs.
I have several ZX Spectrums, this is my dev. machine.......;-)

final0

 

A close-up of the board. It's a 2-layer Pcb, all electronic components are surface mount, the only through hole devices are the connectors and headers.
An LED at the far left signifies unregulated DC input.
The pushbutton switch is a RESET switch for the Spectrum.
The large IC is the 82C55.
There are several labelled test pads on the board for quick access to the power supply voltages (+unreg, +5.3, +3.3) and the 5.000v Vref for the DAC.

final1

 

Further view of the board in operation and connected to the back of my Spectranet card.
You can see along the top connectors my test cables etc for the various analogue inputs/outputs.
For those playing along at home, I used my reflow oven to solder up all the SMD parts, but strictly speaking it could all be done by hand with just the DAC8574 being a bit tricky due to it's TSSOP fine pitch (0.65mm).

final2

 

The pcb, underside view. Most of it is the 0vdc groundplane.
You can also see various mounting holes which, if required, can be used to steady the board in a fixed installation.

final4

 

Major components & I/O summary.
I designed the board to be the same width of the Spectranet card, just to keep things looking nice and even.
Note shown, the I2C connector also doubles to output the Spectrum Unreg V+ and the Spectrum's +5v regulated supply.

pcbannotate

 

Arty-farty shot. In the foreground the power supply section and the ULN2803 digital output buffer. Over to the right is the address decoding IC's (74HCT86 & 74HCT138).

final5

 

Eagle PCB artwork screenshot. Amongat everything else, you can see the data bus (D0 - D7) directly between the 82C55 and the PCA9564, and then down to the Spectrum's edge connector.

pcbfinal

 

When I ordered my PCBs from the fab house I got a few extra, interested?....please contact me.

v1blanks

 

The prototype board.
I had used DIL (non-SMD) parts where I could, but the DAC & ADC in particular required the use of SMD adaptor boards. The 82C55 required a socket.

 

Full development in progress, including my small 8" LCD (composite input) I used for the Spectrum display.

 


Here's my test code for the Pcb, it's designed to be compiled to TAP using ZX Basic compiler (www.boriel.com).


ZX Spectrum Basic - ZX Basic compiler (www.boriel.com)
1' ZX Spectrum Multi IO board2' By Ian Johnston 28/09/20143' 82C55 Port A = 8off Inputs4' 82C55 Port B = 8off Outputs5' 82C55 Port C = 4off Inputs (lower)6'                4off Outputs (upper)7'8' DAC8574IPW 4ch DAC IC, 16bit9' MCP3424 4ch ADC IC, 12-18bit running in 12bit mode10'11' This code is designed to be compiled to TAP using ZX Basic compiler (www.boriel.com)12' Use this .bat file to compile and sent to the Spectrum via a ZX Spectranet interface13' zxb interface2.bas --tap --BASIC --autorun --optimize=314' copy interface2.tap D:\ZX\TNFSD\_files15'16' Spectranet start.bat:17' d:\ZX\Spectranet\tnfsd.exe "d:\ZX\Spectranet\_files"18 19include "input.bas"20 21' Vars22	DIM DacSend(3) AS INTEGER23	DIM DacCh(4) AS UInteger24	DIM digBkeep AS UInteger: LET digBkeep=025	DIM digCkeep AS UInteger: LET digCkeep=026	DIM digSET AS UInteger: LET digSET=027	DIM A AS UByte: LET A=028	DIM B AS UByte: LET B=029	DIM digout AS UInteger: LET digout=030	DIM AdcReceive(2) AS UByte				' 16bit max with 2 vars31	DIM AdcCh AS Float						' 16bit max 0-65535 (this was UInteger but needs to be float for VoltCh)32	DIM Adcval(4) AS Float33	DIM Voltch(4) AS Float34	DIM Adcchconfig(4) AS UInteger: LET Adcchconfig(1)=184: LET Adcchconfig(2)=216: LET Adcchconfig(3)=248: LET Adcchconfig(4)=15235	DIM adc AS UByte=036	DIM dac AS UByte=037	DIM i AS UByte38	DIM onoff AS UByte39	DIM testonoff AS UByte: LET testonoff=040	LET header$    = " Multi I/O Board - Ian Johnston "41	LET blankline$ = "                                "42	LET footer$    = "     A=analogue   D=digital     "43 44'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''45' Setup (runs once at startup only)46 47	' 82C55 ports48	'OUT 59,131		' Control word for prototype board49	OUT 59,145		' Control word = 10010001 (A=in, B=out, C upper=out, C lower=in)50 51	' temporary settings - all outputs off port B52	OUT 27,053	GOSUB turnoffbit54	55	' print stuff once56	PRINT PAPER 6;BRIGHT 1;AT 0,0;header$57	PRINT AT 1,0;blankline$58	PRINT AT 22,0;footer$59	PRINT BRIGHT 1;AT 2,0;"DIG. INPUTS"60	PRINT BRIGHT 1; AT 6,0;"DIG. OUTPUTS"61	PRINT BRIGHT 1; AT 16,0;"ANA. OUTPUTS"62	PRINT BRIGHT 1; AT 10,0;"ANA. INPUTS"63	PRINT AT 7,0;"Port B =     ";"00000000"64	PRINT AT 8,0;"Port C = "     ';"0000    "65	PRINT AT 17,0;"Ch.1 RAW ="66	PRINT AT 18,0;"Ch.2 RAW ="67	PRINT AT 19,0;"Ch.3 RAW ="68	PRINT AT 20,0;"Ch.4 RAW ="69	PRINT AT 11,0;"Ch.1 Vdc ="70	PRINT AT 12,0;"Ch.2 Vdc ="71	PRINT AT 13,0;"Ch.3 Vdc ="72	PRINT AT 14,0;"Ch.4 Vdc ="73	PRINT AT 3,0;"Port A ="74	PRINT AT 4,0;"Port C ="75	PRINT BRIGHT 1;AT 2,13;"76543210"76	PRINT BRIGHT 1;AT 6,13;"76543210"77 78	GOSUB pc9564init						' Initialize I2C controller IC79	80	GOSUB dac8574setup						' Initialize DAC81	82	'FOR delay=1 to 4: NEXT delay83	'LET port$="a"84	'LET bit=085	'LET onoff=186	'GOSUB turnonbit87	'OUT 11,25588	89	' output startup defaults - analogue90	port$="1"91	dac=VAL(port$)92	value$="0"93	DacCh(dac)=val(value$)94	GOSUB dac857495	port$="2"96	dac=VAL(port$)97	value$="0"98	DacCh(dac)=val(value$)99	GOSUB dac8574100	port$="3"101	dac=VAL(port$)102	value$="0"103	DacCh(dac)=val(value$)104	GOSUB dac8574105	port$="4"106	dac=VAL(port$)107	value$="0"108	DacCh(dac)=val(value$)109	GOSUB dac8574		110 111'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''112' Main Loop113 114mainloop:115	' Keyboard116	IF INKEY$ = "d" THEN117		GOSUB inputfromuserdigout118	END IF119	120	IF INKEY$ = "a" THEN121		GOSUB inputfromuseranaout122		GOSUB dac8574setup123		GOSUB dac8574124	END IF125	126	' Digital inputs127	GOSUB digitalinputportA					' full byte port A128	GOSUB digitalinputportC					' lower nibble port C129	130	' ADC131	LET adc=adc+1132	IF adc=5 THEN LET adc=1: END IF133	GOSUB mcp3424chchange					' Config ADC channel134	GOSUB mcp3424							' ADC IC135 136	' Test137	GOSUB anatest138	GOSUB digtest139 140GOTO mainloop141 142'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''143' Test routines - These demo how to use the various I/O144 145anatest:146	IF INKEY$ = "" THEN RETURN: END IF147	IF INKEY$ = "1" THEN LET dac=1: DacCh(dac)=DacCh(dac)+1: GOTO checkdaclimits: END IF148	IF INKEY$ = "q" THEN LET dac=1: DacCh(dac)=DacCh(dac)-1: GOTO checkdaclimits: END IF149	IF INKEY$ = "2" THEN LET dac=2: DacCh(dac)=DacCh(dac)+1: GOTO checkdaclimits: END IF150	IF INKEY$ = "w" THEN LET dac=2: DacCh(dac)=DacCh(dac)-1: GOTO checkdaclimits: END IF151	IF INKEY$ = "3" THEN LET dac=3: DacCh(dac)=DacCh(dac)+1: GOTO checkdaclimits: END IF152	IF INKEY$ = "e" THEN LET dac=3: DacCh(dac)=DacCh(dac)-1: GOTO checkdaclimits: END IF153	IF INKEY$ = "4" THEN LET dac=4: DacCh(dac)=DacCh(dac)+1: GOTO checkdaclimits: END IF154	IF INKEY$ = "r" THEN LET dac=4: DacCh(dac)=DacCh(dac)-1: GOTO checkdaclimits: END IF155	IF INKEY$ = "5" THEN LET dac=1: DacCh(dac)=DacCh(dac)+100: GOTO checkdaclimits: END IF156	IF INKEY$ = "t" THEN LET dac=1: DacCh(dac)=DacCh(dac)-100: GOTO checkdaclimits: END IF157	IF INKEY$ = "6" THEN LET dac=2: DacCh(dac)=DacCh(dac)+100: GOTO checkdaclimits: END IF158	IF INKEY$ = "y" THEN LET dac=2: DacCh(dac)=DacCh(dac)-100: GOTO checkdaclimits: END IF159	IF INKEY$ = "7" THEN LET dac=3: DacCh(dac)=DacCh(dac)+100: GOTO checkdaclimits: END IF160	IF INKEY$ = "u" THEN LET dac=3: DacCh(dac)=DacCh(dac)-100: GOTO checkdaclimits: END IF161	IF INKEY$ = "8" THEN LET dac=4: DacCh(dac)=DacCh(dac)+100: GOTO checkdaclimits: END IF162	IF INKEY$ = "i" THEN LET dac=4: DacCh(dac)=DacCh(dac)-100: GOTO checkdaclimits: END IF163	checkdaclimits:164	IF DacCh(dac)<=0 THEN LET DacCh(dac)=0: END IF165	IF DacCh(dac)>=65535 THEN LET DacCh(dac)=65535: END IF166	GOSUB dac8574setup167	GOSUB dac8574168RETURN169 170digtest:171	' Digital Output - Blinky, toggle of a couple of digital outputs (port b,bit0 & port c,bit 4) direct from code172	LET port$="c"173	LET bit=6174	IF testonoff = 255 THEN LET onoff=1: GOSUB turnonbit: END IF175	IF testonoff = 0 THEN LET onoff=0: GOSUB turnoffbit: END IF176	LET port$="b"177	LET bit=0178	IF testonoff = 255 THEN LET onoff=1: GOSUB turnonbit: END IF179	IF testonoff = 0 THEN LET onoff=0: GOSUB turnoffbit: END IF180	LET port$="b"181	LET bit=1182	IF testonoff = 255 THEN LET onoff=1: GOSUB turnonbit: END IF183	IF testonoff = 0 THEN LET onoff=0: GOSUB turnoffbit: END IF184	LET testonoff = NOT testonoff185RETURN186 187'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''188' Digital inputs (port A of 82C55)189 190digitalinputportA:191	LET digINa = IN 11192	LET A = digINa193	GOSUB binaryconvinputs194	'PRINT AT 3,0;"Port A = ";s$;" Decimal = ";digINa;" "195	PRINT AT 3,13;s$               ': PRINT AT 3,28;digINa;" "196RETURN197 198'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''199' Digital inputs (port C of 82C55 lower nibble)200 201digitalinputportC:202	LET digINc = IN 43203	LET A = digINc204	GOSUB binaryconvinputs205	PRINT AT 4,17;s$(4);s$(5);s$(6);s$(7)		' bit 0, 1, 2 & 3206RETURN207	208 209'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''210' Digital outputs211 212inputfromuserdigout:213	PRINT AT 22,0;"8255 Port (B or C upper nibble)"214	port$=INPUT (1)215	PRINT AT 22,0;"Bit (0 to 7, or 8 for all bits)"216	b$=INPUT (1): LET bit = VAL(b$)217	PRINT AT 22,0;"On/Off (1 or 0)"218	f$=INPUT (1): LET onoff = VAL(f$)219	PRINT AT 22,0;footer$220	IF onoff = 0 THEN GOSUB turnoffbit: END IF221	IF onoff = 1 THEN GOSUB turnonbit: END IF222RETURN223 224' Turn off bit225turnoffbit:226	IF bit=0 THEN LET digSET=254: GOTO bitwiseand: END IF227	IF bit=1 THEN LET digSET=253: GOTO bitwiseand: END IF228	IF bit=2 THEN LET digSET=251: GOTO bitwiseand: END IF229	IF bit=3 THEN LET digSET=247: GOTO bitwiseand: END IF230	IF bit=4 THEN LET digSET=239: GOTO bitwiseand: END IF231	IF bit=5 THEN LET digSET=223: GOTO bitwiseand: END IF232	IF bit=6 THEN LET digSET=191: GOTO bitwiseand: END IF233	IF bit=7 THEN LET digSET=127: GOTO bitwiseand: END IF234 235	' Bitwise AND and retain copy of current outputs in digBkeep or digCkeep236	bitwiseand:237	IF port$="b" THEN238		LET digout = digBkeep bAND digSET239		LET digBkeep = digout240		OUT 27,digout241	END IF242	243	IF port$="c" THEN244		LET digout = digCkeep bAND digSET245		LET digCkeep = digout246		OUT 43,digout247	END IF248 249	GOSUB binaryconvoutputs250	IF port$="b" THEN PRINT AT 7,13;s$: END IF251	IF port$="c" THEN PRINT AT 8,13;s$(0);s$(1);s$(2);s$(3): END IF252RETURN253 254' Turn on bit255turnonbit:256	IF bit=0 THEN LET digSET=1: GOTO bitwiseor: END IF257	IF bit=1 THEN LET digSET=2: GOTO bitwiseor: END IF258	IF bit=2 THEN LET digSET=4: GOTO bitwiseor: END IF259	IF bit=3 THEN LET digSET=8: GOTO bitwiseor: END IF260	IF bit=4 THEN LET digSET=16: GOTO bitwiseor: END IF261	IF bit=5 THEN LET digSET=32: GOTO bitwiseor: END IF262	IF bit=6 THEN LET digSET=64: GOTO bitwiseor: END IF263	IF bit=7 THEN LET digSET=128: GOTO bitwiseor: END IF264 265	' Bitwise OR and retain copy of current outputs in digBkeep or digCkeep266	bitwiseor:267	IF port$="a" THEN OUT 11,digout: END IF268 269	IF port$="b" THEN270		LET digout = digBkeep bOR digSET271		LET digBkeep = digout272		OUT 27,digout273	END IF274	275	IF port$="c" THEN276		LET digout = digCkeep bOR digSET277		LET digCkeep = digout278		OUT 43,digout279	END IF280 281	GOSUB binaryconvoutputs282	IF port$="b" THEN PRINT AT 7,13;s$: END IF283	IF port$="c" THEN PRINT AT 8,13;s$(0);s$(1);s$(2);s$(3): END IF284RETURN285 286'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''287' Binary conversion for digital outputs, s$ is the output288binaryconvoutputs:289	LET s$=""290	LET n=digout291	binloop:292	LET aa = n * 0.5293	IF aa = 0 THEN GOTO addzeros: END IF294	IF aa > INT aa THEN LET s$ = "1" + s$: LET n = ((n - 1)/2): GOTO binloop: END IF295	IF aa <= INT aa THEN LET s$ = "0" + s$: LET n = aa: GOTO binloop: END IF296    addzeros:297	IF LEN s$ = 7 THEN LET s$ = "0" + s$: GOTO endlen: END IF298	IF LEN s$ = 6 THEN LET s$ = "00" + s$: GOTO endlen: END IF299	IF LEN s$ = 5 THEN LET s$ = "000" + s$: GOTO endlen: END IF300	IF LEN s$ = 4 THEN LET s$ = "0000" + s$: GOTO endlen: END IF301	IF LEN s$ = 3 THEN LET s$ = "00000" + s$: GOTO endlen: END IF302	IF LEN s$ = 2 THEN LET s$ = "000000" + s$: GOTO endlen: END IF303	IF LEN s$ = 1 THEN LET s$ = "0000000" + s$: GOTO endlen: END IF304	IF LEN s$ = 0 THEN LET s$ = "00000000": GOTO endlen: END IF305	endlen:306RETURN307 308'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''309' Binary conversion for digital inputs, s$ is the output310 311binaryconvinputs:312	LET s$=""313	LET n=A314	binloopdigin:315	LET aa = n * 0.5316	IF aa = 0 THEN GOTO addzeros: END IF317	IF aa > INT aa THEN LET s$ = "1" + s$: LET n = ((n - 1)/2): GOTO binloopdigin: END IF318	IF aa <= INT aa THEN LET s$ = "0" + s$: LET n = aa: GOTO binloopdigin: END IF319RETURN	320	321	322'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''323' I2C PCS9564324' Initialize325 326pc9564init:327	port$="c"328	bit=7329	onoff=0330	GOSUB turnoffbit331	'OUT 43,0								' Set PC7 low then high to reset PCA9564 IC332	PAUSE 5333	port$="c"334	bit=7335	onoff=1336	GOSUB turnonbit337	'OUT 43,128338	PAUSE 5339	OUT 13,127								' Timeout function (MSB) enabled = 255, disabled = 127340	OUT 45,100								' PCS9564 I2C address = 0x64 = 100341	OUT 61,68								' Clock freq serial = 0x44 = 68 = 88kHz342RETURN343 344'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''345' DAC IC = DAC8574 setup346' Master Tx mode (to set up channel config only)347 348dac8574setup:349	OUT 61,228								' Start command E4 = 228 350 351	' Setup Slave device (Tx mode)352	OUT 29,152								' Load Slave addr + R/W bit = 0 into I2CDAT (DAC8574IPW 0x4c = 76 = 1001100) + R/W bit= 10011000 = 152353	OUT 61,196								' Reset SI and STA bits in I2CCON354	355	OUT 61,212								' Generate stop command, write I2CCON with 0xD4 (212)356RETURN357 358'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''359' DAC IC = DAC8574360' Master Tx mode361 362dac8574:363	OUT 61,228								' Start command E4 = 228 364	OUT 61,196								' Reset SI and STA bits in I2CCON365 366	' scan round all 4 outputs, value of each is in DacCh(dac)367	' Build LSB (max int 65535)368	LET A=DacCh(dac): LET B=255369	LET A = A bAND B370	LET l=A371	' Build MSB372	LET A=DacCh(dac)/256373	LET m=INT A374 375	' Build data array ready for sending to the DAC: CHANNEL, MSB, LSB.376	IF dac=1 THEN LET DacSend(1)=32: LET DacSend(2)=m: LET DacSend(3)=l: PRINT AT 17,13;DacCh(1);"       ": END IF			' Ch.1377	IF dac=2 THEN LET DacSend(1)=34: LET DacSend(2)=m: LET DacSend(3)=l: PRINT AT 18,13;DacCh(2);"       ": END IF			' Ch.2378	IF dac=3 THEN LET DacSend(1)=36: LET DacSend(2)=m: LET DacSend(3)=l: PRINT AT 19,13;DacCh(3);"       ": END IF			' Ch.3379	IF dac=4 THEN LET DacSend(1)=38: LET DacSend(2)=m: LET DacSend(3)=l: PRINT AT 20,13;DacCh(4);"       ": END IF			' Ch.4380 381	' Send data (channel, lsb, lsb)382	FOR i = 1 TO 3383		OUT 29, DacSend(i)						' Load data into I2CDAT, transfer data to DAC384		FOR delay=1 to 4: NEXT delay385		OUT 61,196								' Reset SI and STA bits in I2CCON386		FOR delay=1 to 4: NEXT delay387	NEXT i388 389	OUT 61,212								' Generate stop command, write I2CCON with 0xD4 (212)390RETURN391 392'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''393' User input394 395inputfromuseranaout:396	PRINT AT 22,0;"Analogue Channel (1, 2, 3 or 4)"397	port$=INPUT (1)398	dac=VAL(port$)399	PRINT AT 22,0;"Value (0 to 65535)             "400	value$=INPUT (5)401	PRINT AT 22,0;footer$402	DacCh(dac)=val(value$)403	IF DacCh(dac)<=0 THEN LET DacCh(dac)=0: END IF404	IF DacCh(dac)>=65535 THEN LET DacCh(dac)=65535: END IF405RETURN406	407	408'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''409' ADC IC ADC = MCP3424410' Master Rx mode411 412mcp3424:413	OUT 61,228								' Start command E4 = 228 414 415	' Setup Slave device - receive data from device416	OUT 29,211								' Load Slave addr + R/W bit = 1(read) into I2CDAT (MCP3424 0x69 = 105 = 1101001) + R/W bit= 11010011 = 211417	OUT 61,68								' Reset SI and STA bits in I2CCON (0xC4 = 196), or 68 if AA is set to OFF418	FOR delay=1 to 4: NEXT delay419 420	' Receive data421	FOR i = 1 TO 2422	OUT 61,196								' Reset SI and STA bits in I2CCON423	FOR delay=1 to 4: NEXT delay424	LET AdcReceive(i)=IN 29	' Read byte out425	NEXT i426 427	OUT 61,68								' Reset SI and STA bits in I2CCON428	FOR delay=1 to 4: NEXT delay429	OUT 61,212								' Generate stop command, write I2CCON with 0xD4 (212)430 431	Let MSB = AdcReceive(1) * 256			' <<432	Let LSB = AdcReceive(2) bAND 255		' &433	Let AdcCh = MSB bOR LSB					' |434 435	LET Adcval(adc) = AdcCh	' Load 4 channel values on at a time436 437	LET Voltch(adc) = Adcval(adc) / 3240	' scale display (39k/10k input resistors) to give 0 - 2.048vdc438	LET Voltch(adc)=Voltch(adc)*100: LET Voltch(adc)=INT(Voltch(adc)): LET Voltch(adc)=Voltch(adc)/100		' Round down to 2 DPs439 440	PRINT AT (10+adc),13;Voltch(adc);"    "          '   ;" ";Adcval(adc);"    "441RETURN442 443'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''444' ADC IC ADC = MCP3424445' Master Tx mode (to set up channel config only)446 447mcp3424chchange:448	OUT 61,228								' Start command E4 = 228 449	' Setup Slave device (Tx mode)450	OUT 29,210								' Load Slave addr + R/W bit = 0 into I2CDAT (MCP3424 0x69 = 105 = 1101001) + R/W bit= 11010010 = 210451	OUT 61,196								' Reset SI and STA bits in I2CCON452	' Send data453	PAUSE 1									' Important pause otherwise the data does not go out on the I2C bus properly.454	OUT 29, Adcchconfig(adc)				' configuration byte = initiate,Ch.n,continuous,16-bit,x1Gain455	PAUSE 1456	OUT 61,196								' Reset SI and STA bits in I2CCON457	PAUSE 1458	OUT 61,212								' Generate stop command, write I2CCON with 0xD4 (212)459	' FOR delay=1 to 4: NEXT delay460RETURNCodequote by Ian Johnston